Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ WORKDIR /opt/demoni
RUN mkdir -p bin bridge config /workspace /home/demoni/.demoni

# Install official Gemini CLI (global npm)
ARG GEMINI_CLI_NPM_VERSION=latest
ARG GEMINI_CLI_NPM_VERSION=0.42.0
RUN npm install -g @google/gemini-cli@${GEMINI_CLI_NPM_VERSION}

# Copy Demoni CLI
Expand All @@ -54,6 +54,8 @@ RUN groupadd -g 10001 demoni \

# Environment — bridge will pick an ephemeral port, DEMONI_BRIDGE_PORT only as fallback
ENV PATH="/opt/demoni/bin:${PATH}" \
TERM="xterm-256color" \
COLORTERM="truecolor" \
HOME="/home/demoni" \
DEMONI_HOME="/home/demoni/.demoni" \
DEMONI_BRIDGE_MODE="process" \
Expand Down
216 changes: 216 additions & 0 deletions PRIVACY_LOCKDOWN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
# Demoni Privacy Lockdown

This document describes all privacy hardening applied to Demoni to ensure no data is sent to Google, Gemini, Vertex AI, or any telemetry/tracking services.

## What Is Blocked

### Network Egress
All outbound connections to Google/Gemini hostnames are blocked at the bridge level:

- `generativelanguage.googleapis.com`
- `aiplatform.googleapis.com`
- `oauth2.googleapis.com`
- `accounts.google.com`
- `play.googleapis.com`
- `logging.googleapis.com`
- `monitoring.googleapis.com`
- `cloudtrace.googleapis.com`
- `telemetry.googleapis.com`
- `firebaseinstallations.googleapis.com`
- `firebase-settings.crashlytics.com`
- `crashlyticsreports-pa.googleapis.com`
- `analytics.google.com`
- `google-analytics.com`
- `www.google-analytics.com`
- `stats.g.doubleclick.net`
- `doubleclick.net`
- `gstatic.com`
- `googleapis.com`
- `googleusercontent.com`
- `google.com`

### Model/API Routes
The bridge rejects any request where:
- Model contains "gemini", "google", "vertex", "palm", or "bison"
- URL path targets Gemini-native endpoints (`/v1/models/gemini*`, `/v1beta/models/gemini*`, etc.)
- Auth mode uses Google OAuth or Application Default Credentials

### Telemetry & OpenTelemetry
All telemetry is force-disabled through environment variables in child process:

- `GEMINI_TELEMETRY_ENABLED=false`
- `GEMINI_TELEMETRY_LOG_PROMPTS=false`
- `GEMINI_TELEMETRY_USE_COLLECTOR=false`
- `GEMINI_TELEMETRY_USE_CLI_AUTH=false`
- `GEMINI_TELEMETRY_OTLP_ENDPOINT=` (empty)
- `GEMINI_TELEMETRY_TARGET=local`
- `OTEL_SDK_DISABLED=true`
- `OTEL_TRACES_EXPORTER=none`
- `OTEL_METRICS_EXPORTER=none`
- `OTEL_LOGS_EXPORTER=none`
- All `OTEL_EXPORTER_OTLP_*` endpoints set to empty

### Analytics/Tracking SDKs
All analytics and error tracking SDK environment variables are blanked:

- `SENTRY_DSN=` (empty)
- `DD_API_KEY=` (empty)
- `DD_APP_KEY=` (empty)
- `NEW_RELIC_LICENSE_KEY=` (empty)
- `POSTHOG_API_KEY=` (empty)
- `SEGMENT_WRITE_KEY=` (empty)
- `AMPLITUDE_API_KEY=` (empty)
- `MIXPANEL_TOKEN=` (empty)
- `BUGSNAG_API_KEY=` (empty)
- `ROLLBAR_ACCESS_TOKEN=` (empty)

### Gemini CLI Settings
The Gemini CLI settings file is written to the correct path: `$GEMINI_CLI_HOME/.gemini/settings.json` with:

```json
{
"privacy": { "usageStatisticsEnabled": false },
"telemetry": {
"enabled": false,
"logPrompts": false,
"target": "local",
"otlpEndpoint": ""
}
}
```

### Auto-Update
All automatic update mechanisms are disabled:
- `NO_UPDATE_NOTIFIER=1`
- `NPM_CONFIG_UPDATE_NOTIFIER=false`
- `NPM_CONFIG_AUDIT=false`
- `NPM_CONFIG_FUND=false`
- Dependencies pinned to exact versions (no ^ or ~ ranges)

### Persistent History
History mode defaults to `ephemeral` (memory-only, wiped on exit).
Set `DEMONI_HISTORY_MODE=local` to opt into local-only persistent history.
History files, if created, use 0700/0600 permissions and are never synced.

### Logging
- Default log level is `error` for the CLI
- All API keys, tokens, and secrets are redacted from logs
- Logs are written to `$DEMONI_HOME/log/` with mode 0700/0600
- No prompts, completions, or file contents are logged

### Feedback
All feedback endpoints are disabled. Any feedback mechanism is no-op by default.

### Environment Isolation
- Child processes (Gemini CLI, bridge) receive an **explicit allowlist** of environment variables
- Parent process environment is **not inherited** — only explicitly safe vars are passed
- Google/Gemini/Vertex auth variables are force-blanked or set to false

## What Is Still Allowed

- DeepSeek API calls to `https://api.deepseek.com` (configurable via `DEEPSEEK_BASE_URL`)
- Local bridge communication on `127.0.0.1` only
- Brave Search API (opt-in, requires `BRAVE_API_KEY`)
- Unstructured API (opt-in, requires `UNSTRUCTURED_API_KEY`)
- Gemini CLI binary execution (as wrapper target — telemetry suppressed via env vars and settings)
- Local-only persistent history (opt-in via `DEMONI_HISTORY_MODE=local`)

## How to Verify

### Run Privacy Audit
```bash
npm run privacy:audit
```

### Run Privacy Tests
```bash
npm run test:privacy
```

### Run Full Test Suite
```bash
npm test
```

### Monitor with Packet Capture
```bash
sudo tcpdump -i any -n 'host generativelanguage.googleapis.com or host aiplatform.googleapis.com or host oauth2.googleapis.com'
```

Expected result: **Zero packets**.

### Verify No Telemetry Libraries
```bash
npm ls --production 2>/dev/null | grep -iE "sentry|datadog|newrelic|posthog|amplitude|mixpanel|bugsnag|rollbar|opentelemetry|firebase|google-analytics"
```

Expected result: **No matches**.

## How to Wipe Local Data
```bash
rm -rf ~/.demoni
```

To wipe only history:
```bash
rm -rf ~/.demoni/history
```

## How to Manually Update
```bash
git pull origin main
npm ci --no-audit --no-fund
npm run build
```

Auto-update is disabled. Manual updates only.

## Warnings

### Google Account-Level Settings
Demoni cannot control Google account-level Gemini Apps activity settings. If you use Gemini directly (outside Demoni), you must separately disable:

- [Gemini Apps Activity](https://myactivity.google.com/product/gemini)
- [Google Account History settings](https://myaccount.google.com/data-and-privacy)

### DeepSeek Privacy
DeepSeek processes prompts and completions on their servers. Demoni cannot control DeepSeek's data handling. Review DeepSeek's privacy policy for their data practices.

### Local Bridge Security
The Demoni bridge listens on 127.0.0.1 only and requires a local API key. Ensure no other local processes can access the bridge port.

## Defense-in-Depth Layers

| Layer | Description |
|-------|-------------|
| 1. Environment variable lockdown | Child processes receive explicit allowlist, not inherited env |
| 2. Settings file | Correct path (`.gemini/settings.json`) with 0700/0600 permissions |
| 3. Network egress blocking | 21 Google hostnames blocked in bridge before outbound calls |
| 4. Axios monkey-patch | Every axios request validated against blocklist |
| 5. Model denylist | 11 regex patterns blocking Google/Gemini/Vertex model names |
| 6. URL path blocking | 18 regex patterns blocking Gemini-native API paths |
| 7. Auth blocking | OAuth (ya29.) and ADC tokens rejected with 403 |
| 8. Log redaction | API keys, tokens, secrets scrubbed from all log output |
| 9. No auto-update | Version checks, update notifier, npm audit all disabled |
| 10. Ephemeral history | No persistent data without explicit opt-in |
| 11. Privacy audit script | Automated verification of all above layers |
| 12. CI-enforceable tests | Vitest suite validates privacy guarantees |

## Failure Modes

If the bridge detects an attempted Google/Gemini connection:
- The connection is **blocked**
- Error logged: `[PRIVACY] Blocked outbound request to blocked host: <hostname>`
- The request **fails closed** (no data is sent)

If an unsupported model is requested:
- Request rejected with HTTP 403
- Error: `Blocked Google/Gemini route by privacy policy. Demoni is configured for DeepSeek-only operation.`

If OAuth or ADC auth is attempted:
- Request rejected with HTTP 403
- Error: `Blocked Google OAuth authentication by privacy policy.`

---

*Applied to Demoni v0.2.3+*
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ But your prompts go to DeepSeek V4 models via your `DEEPSEEK_API_KEY` — no Goo
<img src="./demoni.jpg" alt="Demoni" width="80%">
</p>

Current version: `v0.2.1` (from [`VERSION`](./VERSION)).
Current version: `v0.2.3` (from [`VERSION`](./VERSION)).

Release notes: [`RELEASE-NOTES.md`](./RELEASE-NOTES.md)

Expand Down Expand Up @@ -38,8 +38,8 @@ cd demoni
```bash
# Grab the latest release from:
# https://github.com/illdynamics/demoni/releases/latest
unzip demoni-v0.2.1.zip
cd demoni-v0.2.1
unzip demoni-v0.2.3.zip
cd demoni-v0.2.3
./demoni install
```

Expand Down Expand Up @@ -179,7 +179,7 @@ Demoni uses GitHub Actions for continuous integration and automated releases.
- **CI workflow** (`.github/workflows/ci.yml`): Runs on every push and PR — static checks, build & test, package verification, Docker smoke tests.
- **Release workflow** (`.github/workflows/release.yml`): Triggers after CI and only runs on version tags (`v*`). Creates a GitHub Release with a zip archive.

A release is created automatically when a tag matching `v*` (e.g. `v0.2.2`) is pushed and all CI checks pass.
A release is created automatically when a tag matching `v*` (e.g. `v0.2.3`) is pushed and all CI checks pass.

## Acceptance Criteria

Expand Down
78 changes: 78 additions & 0 deletions RELEASE-NOTES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,83 @@
## v0.2.3 (2026-05-15)

### Features
- **Interactive TTY**: The containerized `demoni` wrapper (`~/bin/demoni`) now conditionally allocates a TTY (`-t`) when stdin is a terminal,
restoring full Gemini CLI interactive mode for `demoni` and `demoni -y`. Piped input still works without TTY.
- **Smart TTY passthrough**: TTY is only passed through when the parent process has a terminal on stdin, so piped usage
(`echo "query" | demoni -y`) still works correctly.
- **Host TERM passthrough**: The wrapper now forwards the host's `TERM` and `COLORTERM` environment variables into the container.
The Docker image also sets sensible defaults (`TERM=xterm-256color`, `COLORTERM=truecolor`), eliminating both
`256-color support not detected` and `True color support not detected` startup warnings from the Gemini CLI.

### Fixes
- **Stderr warning suppression**: Fixed `stderr-filter.ts` regex patterns to catch Gemini CLI v0.42.0 warning format changes.
The new format uses a `⚠` emoji prefix (e.g. `⚠ Warning: 256-color...`). Added a `stripPrefix()` helper that strips
leading non-alphanumeric characters before matching, and added `256-color` (dash variant) to the drop patterns.
- **Gemini CLI sync**: Both the host `package.json` (`@google/gemini-cli`) and the `Dockerfile` (`GEMINI_CLI_NPM_VERSION`)
were updated from `0.41.2` to `0.42.0`, matching the host's globally installed version.

### Changes
- `VERSION` — v0.2.3
- `package.json` — `@google/gemini-cli` 0.42.0, version 0.2.3
- `package-lock.json` — Regenerated
- `Dockerfile` — Added `TERM`, `COLORTERM` env vars; `GEMINI_CLI_NPM_VERSION` → 0.42.0
- `~/bin/demoni` (installed wrapper) — `-t` flag + TERM/COLORTERM passthrough
- `demoni` (bootstrap script) — Updated wrapper template
- `src/cli.ts` — v0.2.3, branded banner updated
- `src/stderr-filter.ts` — Fixed pattern matching for emoji-prefixed warnings
- `dist/stderr-filter.js` — Recompiled
- `dist/cli.js` — Recompiled
- `README.md` — v0.2.3 references
- `PRIVACY_LOCKDOWN.md` — v0.2.3 references
- `RELEASE-NOTES.md` — This entry

# Demoni Release Notes

## v0.2.2 (2026-05-14)

### Critical Fix
- **Interactive mode restored**: Fixed TTY detection for Gemini CLI child process.
`demoni` and `demoni -y` now enter interactive mode correctly (like upstream `gemini`).
The root cause was Node.js `child_process.spawn` passing `process.stdin` as a stream
object, which created a pipe instead of inheriting the TTY descriptor. Changed to
`stdio: ['inherit', 'inherit', 'pipe']`.

### Production Hardening (MasterWonq Audit — 9 findings fixed)
- **Release hygiene**: Fixed `check-release-hygiene.sh` — removed `.codeseeq` dev tool from
forbidden list; fixed broken `node_modules` check in the same script.
- **Dependency pinning**: All dependencies pinned to exact versions (no `^` prefixes) in both
`package.json` and `bridge/package.json`. `@google/gemini-cli` pinned to `0.41.2`.
`Dockerfile` `GEMINI_CLI_NPM_VERSION` pinned to `0.41.2`.
- **Startup timeout**: Added 60-second startup timeout in `main()` to prevent indefinite hangs
when bridge fails to start. Calls `die()` with clear error message.
- **SIGINT/SIGTERM async-safety**: Signal handlers no longer call `log()` (avoids lazy
`WriteStream` creation in signal handler). Async cleanup (`doCleanup()`) now completes
before process exit with 5-second timeout backstop. Log stream initialized early in
`main()`.
- **Safer container runtime detection**: `execSync()` replaced with `spawnSync()` + PATH-based
path probing in `findContainerRuntime()`. Eliminates latent command injection risk.
- **Bridge graceful shutdown connection draining**: Added active socket tracking in bridge
server. On shutdown, tracked sockets are destroyed after a drain window (1/3 of shutdown
timeout, max 3s) before force exit.
- **Stderr filter maintenance policy**: Added comprehensive maintenance documentation in
`src/stderr-filter.ts` describing when to run smoke tests, how to add patterns, and when
to deprecate filters.
- **TypeScript hygiene**: Replaced `catch (err: any)` with `catch (err: unknown)` + type guards
in `src/cli.ts`.
- **Test coverage**: Added 3 new CLI integration tests for demoni-branded no-input message,
stderr filter warning suppression, and YOLO deduplication. Total test count: 180 (up from
177).

### Changes
- `scripts/check-release-hygiene.sh` — Fixed release hygiene check
- `package.json` — Pinned all deps, bumped version
- `bridge/package.json` — Pinned all deps
- `Dockerfile` — Pinned `GEMINI_CLI_NPM_VERSION`
- `src/cli.ts` — Startup timeout, SIGINT async-safety, safer container detection, `any` → `unknown`, TTY stdio inheritance fix
- `src/stderr-filter.ts` — Maintenance policy documentation
- `bridge/src/server.ts` — Connection draining for graceful shutdown
- `test/cli.test.ts` — 3 new integration tests

## v0.2.1 (2026-05-12)

### License Change
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v0.2.1
v0.2.3
Loading
Loading