# IS75 — Learnitive Security Audit

Date: 2026-08-31 AEST
Scope: source currently restored in `/mnt/data/learnitive-work/public_html` from BF51.4 plus the current Learnitive reconstruction.

## Executive result

The current source was audited as hostile-input infrastructure. Concrete fixes were applied to archive extraction, terminal networking/container isolation, persistent-terminal entitlements, collaboration link identity, and legacy Ollama fallback presentation. The dependency-free security gate passes.

This is **not production runtime certification**: Docker, browser E2E and the complete dependency-based test matrix are unavailable in this sandbox. The trusted Docker services also still use the host Docker daemon through the `docker` group; a future rootless/containerd isolation migration is recommended as defense in depth.

## Fixed findings

### SEC-75-01 — Template ZIP decompression bomb before post-extract inspection — FIXED

Before this audit, Admin template ZIPs validated path names before `unzip`, but uncompressed file/aggregate limits were checked only after extraction. A high-ratio archive could therefore consume temporary disk before the post-extract walker rejected it.

Fix: `server/template-library.mjs` now parses the ZIP central directory before extraction and rejects:

- oversized individual files;
- oversized aggregate uncompressed content;
- excessive compression ratios;
- excessive entry counts;
- encrypted/unsupported compression entries;
- Zip64-size sentinels outside the supported bounded format;
- duplicate normalized paths;
- Unix symlink entries;
- unsafe absolute/traversal/control-character paths.

The existing post-extract symlink/type/size walk remains as a second boundary.

### SEC-75-02 — Persistent terminal defaulted to unrestricted Docker bridge — FIXED

The restored BF51.4 Console Gateway defaulted `TEXISE_CONSOLE_NETWORK=bridge`, allowing broader outbound/container-network access than the frozen Learnitive policy.

Fix:

- terminal refuses `host`, `bridge`, `default`, or `none` as the configured Pro network;
- production default is the dedicated `learnitive-egress` network;
- deployment provisions an isolated custom bridge with ICC disabled;
- `DOCKER-USER` routes that subnet through `LEARNITIVE-EGRESS`;
- RFC1918, CGNAT, loopback, link-local/metadata, multicast and reserved destinations are rejected;
- only public TCP 80/443, DNS and ICMP are allowed; all remaining egress is rejected;
- outbound SSH therefore fails closed;
- terminal root filesystem is read-only;
- user containers refuse UID/GID 0;
- capabilities remain fully dropped and `no-new-privileges`, CPU, RAM, PID and file quotas remain enforced;
- no Docker socket is mounted into user containers.

Actual iptables/Docker behavior requires deployment-host certification.

### SEC-75-03 — Persistent Docker terminal was authentication-only — FIXED

The restored trusted API allowed any authenticated user to create and continue terminal sessions. Frozen product policy requires the persistent terminal to be Pro.

Fix: creation and every subsequent terminal session operation now recalculate account entitlement server-side and return `pro_entitlement_required` when Pro is absent. CSRF remains required for mutations.

### SEC-75-04 — Legacy Ollama fallback UI state could imply cloud execution — FIXED

The execution path was already restored to fail closed, but the chat surface could still render a historical persisted `fallback` field as “Cloud fallback”.

Fix: the UI no longer displays or routes through legacy fallback state. The internal handler is named `failOllamaWithoutCloudFallback`; local/Ollama failure requires explicit retry or model/provider switch.

## Verified existing boundaries

### Project/archive paths

`project-path.ts` rejects absolute paths, Windows drive paths, backslashes, controls, empty segments and `.`/`..`. Browser ZIP parsing bounds archive size, entries, individual/aggregate uncompressed bytes and compression ratio, verifies local-header filenames/CRC, and never materializes OS symlinks.

### Cloud Compile

The Docker compile worker uses `--network none`, `--read-only`, non-root `65534:65534`, `--cap-drop ALL`, `no-new-privileges`, memory/PID limits and `-no-shell-escape`. The project source mount is read-only and the worker is reached only through a loopback trusted broker with a server-side secret.

### Sessions / CSRF / origins

Session cookies are HttpOnly, SameSite=Lax and Secure on HTTPS. Tokens are stored hashed server-side. Authentication boundaries rotate/destroy sessions where appropriate. Mutating SaaS routes reject cross-site `Sec-Fetch-Site`, validate supplied Origin against the configured public origin, and require a constant-time CSRF token for authenticated mutations.

### Collaboration

Invitation secrets are placed in the URL fragment, not query/server request. Collaboration hosting requires authenticated Pro + CSRF and receives a two-minute HMAC host grant. Anonymous invite participation does not itself produce a terminal/preview entitlement.

### AI / BYOK

Cloud provider keys are browser-held (IndexedDB) and sent directly by the browser adapter to the explicitly selected provider. Provider base URLs reject embedded credentials. Server AI Review exposes no admin OpenAI fallback. Project files/tool results are labelled untrusted in the AI prompt, and the current Agent tool vocabulary explicitly forbids shell/network/destructive Git/credential-disclosure requests.

## Known residual risks / blockers

1. **Host Docker daemon trust boundary.** `texise-console` and `texise-compile` service accounts remain members of the host `docker` group so their trusted gateways can launch containers. User containers do not receive the socket, and model/user inputs cannot choose raw Docker arguments, but compromise of either trusted gateway remains host-high-impact. Rootless Docker or a narrowly scoped container supervisor is recommended for a later infrastructure migration.
2. **Controlled-egress runtime not executed here.** Docker/iptables are unavailable in the sandbox, so the new network rules are source/syntax verified but not packet-tested.
3. **Generic private preview implementation is not source-present after environment resets.** There is no current generic preview gateway to runtime-audit; IS78 must restore/verify it before release.
4. **Learn custom-validator runtime from prior phases is not source-present after resets.** It must be restored and security-tested before IS78/IS79.
5. **Five BF51.4 release-hardening assertions fail in both the untouched baseline and current tree.** They concern evolved AI UI labels/control locations, not newly introduced security regressions. They remain an IS78 regression debt and are not hidden or deleted.
6. Full dependency/typecheck/browser/Docker security certification remains blocked by this environment.

## Dependency-free verification

Passing current security-focused gate:

```text
node --test \
  scripts/learnitive-security-audit.node.test.mjs \
  server/template-library.node.test.mjs \
  server/saas-security.node.test.mjs \
  server/terminal-gateway.node.test.mjs \
  server/terminal-entitlement-security.node.test.mjs \
  packages/core/service-core/src/project-ai-no-cloud-fallback.node.test.mjs
```

Latest combined result: 47 tests passed, 0 failed after the entitlement test is included separately/currently.

Syntax checks:

- `node --check texise-services/console-gateway/server.js` — pass
- `node --check server/template-library.mjs` — pass
- `node --check server/saas-router.mjs` — pass
- `bash -n scripts/lib/texise-deploy.sh` — pass

Baseline comparison hardening suite:

- current: 109/114 pass, 5 fail;
- untouched BF51.4 baseline: the same 5 failures;
- no new failure was introduced by the Learnitive security/rebrand changes.
