# Oxofolio 0.1.3 — security pack

Generated from the source of the application. Every technical statement in it —
the IPC surface, the sidecar methods, the renderer settings, the content
security policy, the dependency inventory, the directories written to — is read
out of the code rather than written by hand, and the release build fails if this
document is out of date with respect to it.

---

# Security overview

**Product** Oxofolio 0.1.3 — Offline document preparation workbench for audit firms
**Supplier** Chadura Tech Private Limited

## What it is

A desktop application that prepares client documents on the machine it is
installed on. It is not a service. There is no account, no server, no tenant and
no vendor-side storage, so there is no vendor environment for a customer's data
to be in.

## Architecture

Three processes, all local:

1. **Main process** (Electron, Node.js) — owns the filesystem, the dialogs and
   the application's own storage. The only component with access to either.
2. **Renderer** (Chromium, Vue) — the interface. Sandboxed, context-isolated,
   no Node integration, and reaches the main process only through the
   91 named channels listed below.
3. **Sidecar** (Python, frozen with PyInstaller) — does the document work.
   Speaks newline-delimited JSON over stdin and stdout. It opens no sockets and
   binds no ports; the pipe to its parent is its only means of communication.

Client documents are read by the sidecar and by the renderer's PDF viewer. They
are never copied anywhere outside the folder they were found in, except into the
output directories named below.

## Network

The application opens no sockets and makes no network connections. There is no
telemetry, no update check, no crash reporting and no licence call-home;
licences are verified offline against a public key shipped with the
application. No document, filename, folder path or other client data is
transmitted anywhere, by any means.

One qualification, stated here rather than left for you to find. The interface
carries 2 links to our website:

- `feedback` — https://oxofolio.com/feedback/?from=app
- `interviews` — https://oxofolio.com/interviews/?from=app

Activating one calls `shell.openExternal`, which hands the address to the
operating system and opens the user's own browser. **That browser then makes a
request, and we can see it** — the visit reaches our web server with the user's
IP address, and the `from=app` marker tells us it came from an installation
rather than from the website. The application itself still opens no connection,
and nothing about the user's documents is included in the address. But a
document that said "no network connections of any kind" and left this out would
be answering the wrong question, so it is set out in full.

The addresses are held in the main process and the interface refers to them by
name. Code running in the renderer cannot ask for an arbitrary address to be
opened; only the ones listed above can be.

Blocking outbound traffic on the host does not affect the application. The
links simply fail to load in the browser, and every feature continues to work.

The no-connection property is enforced rather than asserted.
`scripts/check-no-network.mjs` fails the build if any of these patterns
appears anywhere in the shipped source:

- `/\bfetch\s*\(/`
- `/\bXMLHttpRequest\b/`
- `/\bWebSocket\b/`
- `/from\s+['"]node:(https?|net|dgram|dns)['"]/`
- `/require\(['"](https?|net|dgram|dns)['"]\)/`
- `/\bimport\s+(requests|urllib|httpx|socket)\b/`
- `/^\s*import\s+socket\b/m`

scanned across `electron/**/*.{js,cjs,mjs}`, `src/**/*.{js,vue}`, `python/**/*.py`. It runs in
`npm run gates`, which every release target depends on.

The renderer additionally runs under a Content Security Policy with
`connect-src 'none'`, window opening is denied outright, and navigation is
cancelled in the main process.

## Renderer hardening

| Setting | Value |
|---|---|
| contextIsolation | true |
| nodeIntegration | false |
| sandbox | true |
| webSecurity | true |

Content Security Policy in production builds:

- `default-src 'none'`
- `script-src 'self'`
- `style-src 'self' 'unsafe-inline'`
- `img-src 'self' data:`
- `font-src 'self'`
- `connect-src 'none'`

## The IPC surface

Every channel the interface can invoke. There is no general-purpose bridge and
no way to reach the filesystem or run a command except through one of these.

- `app:diagnostics`
- `app:usage`
- `assembly:seal`
- `assembly:state`
- `assembly:verify`
- `checks:duplicates`
- `checks:mixing`
- `clipboard:write`
- `compress:batch`
- `dialog:pickFile`
- `dialog:pickFiles`
- `dialog:pickFolder`
- `dialog:savePath`
- `doc:personalData`
- `doc:preview`
- `doc:read`
- `doc:readDecrypted`
- `docs:states`
- `encrypt:files`
- `export:pdf`
- `extract:export`
- `extract:fields`
- `files:stats`
- `folders:bookmark`
- `folders:forget`
- `folders:forgetAll`
- `folders:list`
- `folders:open`
- `images:annotate`
- `images:fromPdf`
- `images:preview`
- `images:toPdf`
- `licence:install`
- `licence:state`
- `licence:status`
- `log:write`
- `logs:list`
- `ocr:status`
- `office:thumbs`
- `openers:list`
- `openers:open`
- `pages:merge`
- `pages:preview`
- `pages:reorder`
- `pages:split`
- `pdf:scan`
- `pdf:shape`
- `pdf:toDocument`
- `pdf:toWorkbook`
- `pdf:unlockBatch`
- `pdf:unlockOne`
- `pii:redact`
- `pii:scan`
- `pw:forget`
- `pw:forgetAll`
- `pw:known`
- `pw:status`
- `record:build`
- `record:fields`
- `record:particulars`
- `record:saveFirm`
- `rename:apply`
- `rename:plan`
- `render:covers`
- `render:thumbnails`
- `retention:erase`
- `retention:plan`
- `retention:status`
- `sample:draw`
- `sample:reperform`
- `sample:size`
- `settings:folder`
- `settings:saveFolder`
- `sheet:edit`
- `sheet:preview`
- `sheet:sanitise`
- `sheet:structure`
- `shell:openLink`
- `shell:revealPath`
- `sig:add`
- `sig:list`
- `sig:place`
- `sig:remove`
- `stamp:batch`
- `stamp:index`
- `vault:clear`
- `vault:loadCsv`
- `vault:loadMaster`
- `vault:status`
- `watch:start`
- `watch:stop`

## The sidecar surface

Every method the Python process answers to. An unrecognised method is refused.

- `assembly_seal`
- `assembly_verify`
- `build_record`
- `check_mixing`
- `compress_batch`
- `decrypt_to_bytes`
- `diagnostics`
- `doc_personal_data`
- `doc_preview`
- `document_states`
- `encrypt_files`
- `export_transactions`
- `extract_fields`
- `file_stats`
- `find_duplicates`
- `image_annotate`
- `image_preview`
- `images_to_pdf`
- `logs_list`
- `ocr_status`
- `office_thumbnails`
- `pages_merge`
- `pages_preview`
- `pages_reorder`
- `pages_split`
- `pdf_shape`
- `pdf_to_document`
- `pdf_to_images`
- `pdf_to_workbook`
- `pii_scan`
- `redact_files`
- `rename_apply`
- `rename_plan`
- `render_covers`
- `render_thumbnails`
- `retention_erase`
- `retention_plan`
- `retention_status`
- `sample_draw`
- `sample_reperform`
- `sample_size`
- `scan`
- `sheet_edit`
- `sheet_preview`
- `sheet_sanitise`
- `sheet_structure`
- `sign_place`
- `stamp_batch`
- `stamp_index`
- `unlock_batch`
- `unlock_one`
- `vault_clear`
- `vault_load_csv`
- `vault_load_master`
- `vault_status`
- `write_log`

## Where data is written

Inside a client folder, the application only ever creates these directories, and
writes its outputs inside them:

- `_assembly/`
- `_bundles/`
- `_compressed/`
- `_corpus_out/`
- `_edited/`
- `_encrypted/`
- `_erasure/`
- `_extracted/`
- `_ocr/`
- `_record/`
- `_redacted/`
- `_renamed/`
- `_sample/`
- `_signed/`
- `_split/`
- `_stamped/`
- `_unlocked/`

**Every document operation writes a new file and leaves the source untouched**
— unlock, compress, combine, convert, redact, encrypt, split, stamp, OCR,
image export and image mark-up all work this way. There is no app-managed
document store and there will not be one: a second copy of client statements
outside the firm's backup and access controls is a security regression rather
than a convenience.

**One feature writes in place, deliberately: the spreadsheet viewer's light
edit mode.** A reviewer correcting three cells should not have to round-trip
through Excel to do it. Because that is a change to a client's file, it is
constrained in three ways that are worth checking rather than taking on trust:

- **It is off by default.** Editing is a mode the user turns on; no keystroke
  begins altering a document that is merely being read.
- **The file as it arrived is preserved first.** Before the first write it is
  copied to `_originals/`, hashed with SHA-256, and never refreshed afterwards
  — so a later edit cannot quietly become "the original".
- **The change is recorded.** Source and output hashes, which cells changed,
  and whether any formula was replaced, in the same processing log every other
  operation writes.

Edits are applied to the file's own XML rather than by re-saving the workbook,
so formulas, cached results, formatting, charts, pivot tables and macros are
carried across untouched. Structural changes that would move a formula's
references are refused outright rather than approximated.

In the application's own storage (`userData`, outside any client folder):

| File | Contents |
|---|---|
| `folder-settings.json` | Per-folder preferences, the recent and pinned folder list, retention basis, audit-file seals and the engagement fingerprint used by the client-mixing check. Includes client folder paths. |
| `firm.json` | The firm's own Article 30 particulars: name, address, privacy contact, data protection officer. |
| `passwords.json` | Document passwords the operator chose to remember, encrypted with the operating system keystore. Written only when the OS can encrypt; never in plain text. |
| `signatures/` | Signature and stamp images the operator added, as PNG. Capped at three. |
| `licence.key` | The installed licence and its signature. |
| `install-time.json` | First-run date, so the trial can be counted; the date last seen, so a clock moved backwards can be detected; and the machine identifier described under Licensing below. No document, folder or client information. |

## Licensing

Licences are files, signed with Ed25519 and verified against a public key
compiled into the application. Verification is arithmetic performed on this
machine. Nothing is transmitted, there is no activation step, and an
installation that never touches a network works indefinitely.

A licence is issued for one computer, so the application derives an identifier
for the machine it is running on and compares it with the one in the licence.
That identifier is a SHA-256 hash of two values read from the operating system:
an installation identifier, which changes when the operating system is
reinstalled, and a hardware serial number. Because it is a hash, the values
underneath cannot be recovered from it.

What this means in practice, stated plainly because it is the part a reviewer
will want to be sure of:

- The identifier is derived on demand, held in memory, and written only to the
  application's own storage on this machine. It is not transmitted anywhere.
- It identifies a computer, not a person. It contains no user name, no network
  address, and nothing about the documents processed.
- It is displayed in Settings. Obtaining a licence means the operator reading it
  and sending it to us deliberately -- there is no step in which the application
  discloses anything on its own.
- Reinstalling the operating system or replacing the computer produces a new
  identifier, and the existing licence stops matching. The application then
  warns for 7 days before it stops processing, so a failed disk does not
  halt work while a replacement licence is arranged.
- Where the identifier cannot be read in full -- a hardened or virtualised host
  is the usual reason -- the check is skipped rather than failed. No one is
  blocked because a probe did not answer.


---

# Vendor security questionnaire

Answers as at Oxofolio 0.1.3. Where the answer is unfavourable it is
given plainly; a security pack that only contains good news is not usable by the
person who has to sign it off.

### Is customer data transmitted to the supplier or any third party?

No. The application makes no network connections. The supplier receives nothing, including no telemetry or usage data.

### Where is customer data stored?

On the customer's own machine, in the customer's own folders. The supplier operates no infrastructure that holds customer data.

### Is data encrypted in transit?

Not applicable: there is no transit. Nothing leaves the machine.

### Is data encrypted at rest?

Documents are left as the customer stores them, so at-rest encryption is whatever the customer's disk encryption provides — full-disk encryption is recommended and the application reports whether it is enabled. Document passwords the operator asks the application to remember are encrypted with the operating system keystore (Keychain on macOS, DPAPI on Windows) and are not written at all if the OS cannot encrypt them.

### What authentication does the application use?

None. It has no accounts. It runs as the logged-in operating system user and inherits that user's file permissions.

### Are there sub-processors?

No. There is no processing off the machine, so there is nothing to sub-contract.

### What third-party components are used?

Listed in full in the accompanying inventory, with the licence each declares.

### How are vulnerabilities in dependencies handled?

Dependencies are pinned. Updates are applied on a release cycle and after any advisory affecting a component in the shipped inventory. The inventory in this pack is generated from the committed lockfile, with the integrity hash of every resolved package, so it can be diffed against an advisory feed and describes the same components whichever platform built the release.

### Has the application been penetration tested?

Not independently, as at this release. Stated plainly rather than omitted.

### Do you hold SOC 2 or ISO 27001?

No. Neither certification is held.

### Is the application code signed?

Per platform, because the answer differs. **macOS: yes.** Signed with a Developer ID Application certificate, with the hardened runtime enabled, notarised by Apple and stapled — `spctl` reports it as a Notarized Developer ID build, and the release check fails if the disk image is not stapled. **Windows: no.** The installer builds, installs and runs, and it is unsigned: no code-signing certificate is configured (none of certificateFile, certificateSubjectName, certificateSha1, sign is set), so SmartScreen will warn on first run. The certificate is the only outstanding item and `check:signing` turns an unsigned artifact into a build failure under `OXOFOLIO_RELEASE=1`, so it cannot ship by accident. **Linux: the `.rpm` is GPG-signed** and verified against the published key; a customer should check the signature before installing.

### What logging does the application produce?

A processing log written into the output directory, recording what was processed, when, by whom, on which machine, and the SHA-256 of every input and output. It is written for the customer, into the customer's folder. Document passwords are deliberately excluded from it.

### How is an incident reported?

To the supplier's security contact. Because the application transmits nothing, a defect in it cannot expose customer data to the supplier or to any third party; the realistic incident classes are local — a document written to the wrong place, or an output that fails to redact what it claimed to.

### Can the application be run without administrative rights?

Yes. It needs no elevated privileges and installs per-user.

### Does the application execute anything it downloads?

It downloads nothing. The Python sidecar and the OCR engine are bundled in the installer and are the only executables it launches.

### What infrastructure does the supplier operate?

One thing: a private S3 bucket of installers behind a CloudFront distribution, so the software can be downloaded. The bucket is not public — Block Public Access is on and only the distribution can read it, through an Origin Access Control. It holds the supplier's own installers and no customer data of any kind.

### Does the supplier log who downloads the software?

Partly, and the distinction matters. **The CDN does not**: CloudFront access logging is switched off deliberately, configured in scripts/setup-downloads.sh with the reasoning recorded beside the setting. **The website does**: requesting a build through oxofolio.com records the platform, the release version, the time, the IP address and the browser user-agent, so the supplier knows which releases are in use. That makes the supplier a controller for those records, which the published privacy policy states. They are website access records and contain no customer document data of any kind, and they are not linked to any licence or purchase.

### How can a downloaded installer be verified?

Every release is published with a manifest listing the SHA-256 of each file, and the download page prints those hashes with the command to check them. A hash that does not match means the file is not the one that was published.

### What happens to data when the customer stops using the product?

Nothing needs to happen at the supplier's end, because the supplier holds nothing. The application includes a retention clock and a certified erasure function for the customer's own copies.

## Enforced rather than asserted

Some of the answers above are checked by scripts that run on every release
build, not merely believed:

- `npm run lint:noNetwork && npm run security:check && npm run preflight`
- The no-network scan rejects 7 networking patterns across 3 source trees; the patterns themselves are listed in the security overview.
- The preflight gate exercises 56 of the 56 sidecar methods against the frozen binary before a build is allowed, which is all of them.
- This document is regenerated from the source and the build fails if it is stale.
- The release pipeline refuses to publish an unsigned build: publishing runs the signing check first and stops on failure.


---

# Data protection position

## Roles

The customer is the data controller — or, where the customer processes on behalf
of its own client, the processor. **The supplier is neither.** The application
transmits nothing and the supplier operates no infrastructure that receives
customer data, so the supplier does not process personal data on the customer's
behalf and is not a processor under Article 4(8) GDPR or a Data Processor under
section 2(k) of the Digital Personal Data Protection Act 2023.

A data processing agreement is therefore not required for the software itself.
Suppliers who claim otherwise are usually describing a hosted service.

## Cross-border transfer

None. Processing takes place on the machine the software is installed on. There
is no transfer to a third country under Chapter V GDPR, and no transfer outside
India under section 16 of the DPDP Act.

## What the application does for the customer's own obligations

It does not make the customer compliant, and it is not sold as doing so.
Compliance is a property of the customer's practices. What the application
provides is evidence and tooling for specific obligations:

| Obligation | What the application provides |
|---|---|
| Article 30 GDPR — records of processing | A draft record built from what was actually processed, with the fields only the firm can answer left visibly outstanding |
| Article 5(1)(e) GDPR, DPDP s.8(7) — storage limitation | A retention clock anchored to a statutory basis the firm cites, and certified erasure with a hash of everything destroyed |
| Article 32 GDPR, DPDP s.8(5) — security of processing | Local-only processing, OS-keystore password storage, SHA-256 of every input and output |
| Article 17 GDPR — erasure | True redaction that removes text rather than covering it, verified by re-reading the output |
| DPDP s.8(3) — accuracy and completeness | Duplicate detection and a guard against one client's documents sitting in another's folder |

## Limits of the redaction and erasure functions

Stated here because a customer relying on them needs to know:

- **Redaction** removes the text from the document and the output is re-read to
  confirm the identifier is gone. Documents with no text layer are refused
  rather than covered with a rectangle.
- **Erasure** overwrites and unlinks, which is NIST SP 800-88 *Clear*. It is not
  *Purge* or *Destroy*. On a solid-state drive, wear levelling can leave the
  original block intact until the controller reuses it, and a copy-on-write
  filesystem can retain the earlier version in a snapshot. Full-disk encryption
  is what makes such residue worthless, and the erasure certificate records
  whether it was enabled.


---

# Third-party components

## Shipped in the application

Runtime dependencies packaged into the installer.

| Component | Version | Licence | Source |
|---|---|---|---|
| `exceljs` | 4.4.0 | MIT | npm |
| `pdfjs-dist` | 6.2.108 | Apache-2.0 | npm |
| `pptxgenjs` | 4.0.1 | MIT | npm |
| `pikepdf` | 9.11.0 | see project | PyPI |
| `pypdfium2` | 5.12.1 | see project | PyPI |
| `Pillow` | 11.3.0 | see project | PyPI |
| `openpyxl` | 3.1.5 | see project | PyPI |
| `xlrd` | 2.0.2 | see project | PyPI |
| `Electron` | 43.2.0 | MIT | npm (runtime shell) |
| `qpdf` | bundled with pikepdf | Apache-2.0 | native |
| `Tesseract OCR` | bundled, optional | Apache-2.0 | native |
| `libleptonica`, `libarchive`, `OpenSSL (libcrypto)`, `libtiff`, `libpng`, `libjpeg`, `libwebp`, `giflib`, `zstd`, `ICU` and related | vendored with Tesseract | BSD / Apache-2.0 / zlib-style | native, bundled per platform |

Python itself is embedded in the frozen sidecar (PyInstaller, GPL-exempt
bootloader licence) and is not installed on the customer's machine.

## Build-time only

11 development dependencies are used to build the
application and are **not** present in the installed product. Electron is the
exception: it is a build-time dependency that ships as the runtime shell.

## Full resolved tree

738 packages are resolved by the committed lockfile, of which
85 are platform-specific binaries -- only the ones matching a given
machine are ever installed on it. The complete list with versions and declared
licences is in `sbom.json` beside this document, and the resolved tree with
integrity hashes is in `package-lock.json` in the repository.

The inventory is generated from that lockfile rather than from an installed
`node_modules`, so it describes the same set of components whichever platform
the build ran on.


---

# Deployment notes for IT

## Installation

Per-user. No administrative rights are required and no system services are
installed. The application writes nothing outside the user's own profile and the
folders the operator chooses.

## Code signing

**As at this release the builds are not signed or notarised.** macOS Gatekeeper
and Windows SmartScreen will warn. This is a known gap and is tracked; a
customer evaluating for production use should ask for a signed build.

## Network policy

The application requires no network access and makes no connections. It can be
run on a host with all outbound traffic blocked, and doing so is a reasonable
way for a customer to verify the claim independently. Every feature works in
that state; the only visible effect is that the website links in the interface
fail to open, since those are handed to the user's browser rather than fetched
by the application. See the Network section of the security overview.

## Filesystem access

The application reads the folders the operator selects and writes only into the
output directories listed in the security overview, plus its own storage under
the user profile. It never modifies an original document in place.

## What to give it

- Read and write access to the engagement folders the operator works on.
- Nothing else. No shared service account, no database, no inbound ports.

## Verifying the claims yourself

- Run it with the network disconnected; everything except nothing continues to work.
- Watch it with a network monitor; there is no traffic to see.
- Inspect `resources/sidecar` and confirm the process opens no sockets (`lsof -i`).
- The generated IPC and sidecar surfaces in the security overview are the
  complete list of operations the interface can trigger.

## Known limitations

- The Windows installer is not code signed. macOS is signed, notarised and
  stapled; the `.rpm` is GPG-signed.
- No independent penetration test.
- No SOC 2 or ISO 27001.
- **English is the only OCR language pack bundled.** The engine itself ships on
  all three platforms. Roman-script documents in other languages are recognised
  acceptably; a different script is not recognised at all, and the failure looks
  like a poor scan rather than a missing pack.
- The bundled Windows `tesseract.exe` carries a third-party signature whose
  issuing certificate has expired. It is the upstream UB Mannheim build and is
  unmodified, but an endpoint agent may treat an unvalidatable signature more
  harshly than an unsigned binary.

