Skip to content

feat: ship download-ready Nova Desktop Node#3

Open
warheart1984-ctrl wants to merge 3 commits into
mainfrom
codex/nova-desktop-node-quickstart
Open

feat: ship download-ready Nova Desktop Node#3
warheart1984-ctrl wants to merge 3 commits into
mainfrom
codex/nova-desktop-node-quickstart

Conversation

@warheart1984-ctrl

@warheart1984-ctrl warheart1984-ctrl commented Jun 28, 2026

Copy link
Copy Markdown
Owner

What

Publishes the updated Lawful Nova shell as a source-only, download-ready standalone branch for agentic-coding-agent.

This branch includes:

  • the governed Nova Node backend under nova/node
  • the Nova Desktop Electron/Monaco app under desktop
  • Windows and macOS/Linux quickstart scripts
  • packaging scripts for local Windows/macOS zips
  • GITHUB-10-MINUTE-START.md for clone-to-running setup
  • a GitHub smoke workflow that verifies the download path and uploads source bundles

Why

The goal is that someone can download or clone this repository from GitHub and have the Node plus desktop shell working in about ten minutes, without cloud orchestration, committed binaries, node_modules, or generated zip artifacts.

How to run

Windows:

powershell -NoProfile -ExecutionPolicy Bypass -File .\quickstart.ps1

macOS/Linux:

chmod +x quickstart.sh bin/nova setup/*.sh
./quickstart.sh

Then open:

  • Node API: http://127.0.0.1:8080
  • Nova Desktop: launched by npm start from desktop/

Verification

Source workspace:

  • ..\.venv\Scripts\python.exe -m pytest tests -q -> 61 passed, 1 warning
  • npm test in desktop/ -> 8 passed

Standalone export:

  • E:\project-infi\.venv\Scripts\python.exe -m pytest tests -q -> 61 passed, 1 warning
  • npm ci in desktop/ -> installed cleanly, 0 vulnerabilities
  • npm test in desktop/ -> 8 passed
  • git diff --cached --check before commit -> OK
  • generated/binary artifact scan before commit -> OK

Notes

This is intentionally a source-only export of lawful-nova-shell into the repository root. Generated zips, Electron build outputs, runtime state, node_modules, and packaged executables are excluded. The included GitHub workflow can produce downloadable source artifacts from the branch.


Open in Devin Review

@warheart1984-ctrl

Copy link
Copy Markdown
Owner Author

CI note from Codex: the initial download-smoke workflow did not execute any checkout/test steps.

GitHub returned this check-run annotation:

The job was not started because your account is locked due to a billing issue.

Local and standalone-export verification passed before publishing; rerun the workflow after the GitHub billing/account lock is cleared.

@warheart1984-ctrl
warheart1984-ctrl marked this pull request as ready for review July 18, 2026 10:57
@cursor

cursor Bot commented Jul 18, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 7 potential issues.

View 1 additional finding in Devin Review.

Open in Devin Review

Comment on lines +1 to +4
let config = {
nodeUrl: "http://localhost:8000",
model: "qwen2.5-coder:3b",
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Desktop app points at the wrong port and shows the node as offline out of the box

The desktop program defaults its backend address to port 8000 (nodeUrl: "http://localhost:8000" at desktop/core/node-client.js:2, also the fallback at desktop/core/node-client.js:91), but the governed Node backend actually serves all of its endpoints on port 8080, so a freshly downloaded desktop cannot reach the node until the user manually edits the settings.
Impact: After following the quickstart, the desktop shows "Node offline" and no tools, receipts, replay, or status work until the URL is changed by hand, breaking the advertised ten-minute setup.

Port mismatch between desktop client and node server

The backend is launched via python -m nova.api, whose main() binds NOVA_PORT defaulting to 8080 (nova/api.py:651), and all node routes (/node/status, /node/tool, etc.) are registered on that same app. Every user-facing doc states the Node API is http://127.0.0.1:8080 (README services table, WINDOWS-DESKTOP.md:43, GITHUB-10-MINUTE-START.md:74). Port 8000 is the optional AAIS service, not the node. desktop/renderer/status.js calls window.nodeAPI.status() which hits ${config.nodeUrl}/node/status = http://localhost:8000/node/status on startup and every 5s; the request fails, so the heartbeat turns red and the panel shows "Node offline". Nothing in settings.js, model-select.js, or index.js overrides the default to 8080, so the mismatch persists until the user types a new URL in the settings strip.

Suggested change
let config = {
nodeUrl: "http://localhost:8000",
model: "qwen2.5-coder:3b",
};
let config = {
nodeUrl: "http://localhost:8080",
model: "qwen2.5-coder:3b",
};
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment thread nova/api.py
Comment on lines +413 to +424
app.include_router(node_status_routes.router)
app.include_router(node_event_routes.router)
app.include_router(node_agent_routes.router)
app.include_router(node_evidence_routes.router)
app.include_router(node_federation_routes.router)
app.include_router(node_submit_routes.router)
app.include_router(node_tool_routes.router)
app.include_router(node_replay_routes.router)
app.include_router(node_mesh_routes.router)
app.include_router(node_alert_routes.router)
app.include_router(node_policy_routes.router)
app.include_router(node_conformance_routes.router)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟨 Governed tool and evidence endpoints bypass the API-key gate

In nova/api.py, only the directly-decorated routes (/node/status, /node/submit, /node/result) use Depends(_require_api_key). All the router-based endpoints included afterwards — /node/tool, /node/replay/{trace_id}, /node/verify/{trace_id}, /node/compare-receipts, /node/events, /node/evidence-bundle, /node/gossip, etc. — are registered with no authentication dependency. When an operator sets NOVA_API_KEY expecting the node to be protected, the model-invoking coding tool endpoint (nova/node/tools/routes.py:38) and the federation/gossip endpoints remain fully open.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment thread nova/api.py
Comment on lines +430 to +433
signing_secret="local-api-secret",
provider=_build_provider(),
)
turn = llm.ask(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟨 Governance receipts signed with a hardcoded HMAC secret

The in-process Lawful LLM receipts are HMAC-signed using a constant secret baked into the source: signing_secret="local-api-secret" in nova/api.py:431-432, signing_secret="local-dev-secret" in nova/cli.py, and "gate-secret"/"chain-gate-secret" in the productization gate. Because the signing key is public in the repository, the receipt_verified / verify_receipt result provides no real tamper-evidence: anyone can forge a valid signature for an arbitrary receipt payload.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment thread nova/node/identity.py
Comment on lines +54 to +81
def sign_payload(payload: dict[str, Any], private_key: dict[str, Any] | None = None) -> str | None:
key = private_key or load_operator_private_key()
if not key:
return None
n = int(str(key["n"]), 0)
d = int(str(key["d"]), 0)
digest = int.from_bytes(_canonical_digest(payload), "big") % n
signature = pow(digest, d, n)
byte_len = max(1, (n.bit_length() + 7) // 8)
return base64.b64encode(signature.to_bytes(byte_len, "big")).decode("ascii")


def verify_payload_signature(
payload: dict[str, Any],
signature: str | None,
public_key: dict[str, Any] | None = None,
) -> bool:
key = public_key or load_operator_public_key()
if not key or not signature:
return False
try:
n = int(str(key["n"]), 0)
e = int(str(key["e"]), 0)
sig_int = int.from_bytes(base64.b64decode(signature), "big")
digest = int.from_bytes(_canonical_digest(payload), "big") % n
return pow(sig_int, e, n) == digest
except Exception:
return False

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟨 Home-rolled textbook RSA (no padding) used for federation trust signatures

nova/node/identity.py implements RSA sign/verify manually: sign_payload computes pow(digest, d, n) and verify_payload_signature checks pow(sig_int, e, n) == digest, where digest = int.from_bytes(sha256(...)) % n. This is textbook RSA with no padding scheme (no PKCS#1 v1.5 / PSS), and the digest is reduced modulo n. These signatures gate peer trust_level decisions and policy-hash consensus in federation.py/mesh.py.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +12 to +16
def load_receipt(trace_id: str) -> dict[str, Any]:
path = runtime_dir() / "tool-receipts" / f"{trace_id}.json"
if not path.exists():
raise FileNotFoundError(f"Receipt not found: {path}")
return json.loads(path.read_text(encoding="utf-8"))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟨 trace_id path parameter interpolated directly into filesystem paths

nova/node/tools/replay_coding.py:12-16 builds runtime_dir() / "tool-receipts" / f"{trace_id}.json" directly from the caller-supplied trace_id, and nova/node/submit.py similarly builds results/{trace_id}.json. These are reached from unauthenticated path routes like /node/verify/{trace_id} and /node/replay/{trace_id}. If a trace_id containing path traversal sequences reaches the loader, it could read/write files outside the intended runtime directory.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; font-src 'self' data:;">

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟨 Electron renderer CSP allows inline scripts alongside broad filesystem bridge

desktop/renderer/index.html:5 sets a CSP of script-src 'self' 'unsafe-inline', while desktop/preload.js exposes fileAPI.readFile, fileAPI.writeFile, and fileAPI.createProject (arbitrary paths) to the renderer. The renderer also builds DOM via innerHTML in several panels. 'unsafe-inline' weakens the primary XSS defense; if any injected/model-returned content reaches an unescaped sink, it could execute and drive the exposed file read/write bridge.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment thread setup/bootstrap.sh
Comment on lines +106 to +111
if [[ -f "$HOME/.novarc" && ! -f "$HOME/.novarc.bak" ]]; then
cp "$HOME/.novarc" "$HOME/.novarc.bak"
warn "Backed up $HOME/.novarc"
fi
cp "$REPO_ROOT/config/.novarc" "$HOME/.novarc"
success "Deployed: $HOME/.novarc"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟨 Bootstrap overwrites the operator's ~/.novarc which repo policy marks as sacred

setup/bootstrap.sh:106-111 now unconditionally copies the repo template over $HOME/.novarc (cp "$REPO_ROOT/config/.novarc" "$HOME/.novarc"). AGENTS.md explicitly lists ~/.novarc (and ~/.novarc.ps1, .env, .env.local) under Never: "Overwrite ... these are sacred." Although a one-time .bak backup is made, the live user config (which can contain operator paths/secrets) is still clobbered on every run.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant