28 KiB
Security Audit Report
1. Executive Summary
This fix-mode audit reviewed Nexus Terminal, a Wails desktop client with a Go backend and JavaScript frontend for SSH, Telnet, RAW TCP, VNC, SFTP, GitHub, recordings, local shells, encrypted secrets, and profile import/export.
The highest-risk issues fixed in this run were unsafe profile persistence/import validation, a local DOM XSS trigger in the file-drop modal, memory-exhaustion paths in import and drag-drop upload flows, vulnerable dependency versions, and release tooling that installed unpinned build dependencies. I also removed the stale insecure SSH connection helper so future code cannot silently bypass host-key verification.
Post-fix verification passed: go test ./..., focused Go tests, npm run build, npm audit --json, go run golang.org/x/vuln/cmd/govulncheck@latest ./..., bash -n build.sh release.sh, and git diff --check.
Residual risk remains where users intentionally choose insecure protocols or workflows, especially direct VNC without SSH tunnelling. No high-confidence secrets or private keys were found in the repository search performed during this audit.
2. Scope Reviewed
Reviewed code and configuration:
- Go backend under
internal/: API bindings, sessions, SSH, VNC, SFTP, storage, secrets, export/import, validation, updater, recordings, local shell, GitHub client, raw and telnet clients. - Frontend JavaScript under
frontend/src/js, especially file drop, terminal, settings, GitHub, profile, SFTP, VNC, notification, and import/export flows. - Dependency manifests and lockfiles:
go.mod,go.sum,frontend/package.json,frontend/package-lock.json. - Build/release configuration:
build.sh,release.sh,.github/workflows/release-builds.yml,wails.json. - Existing tests under
internal/**/*_test.go.
Not reviewed with live external testing:
- Real SSH/VNC/GitHub endpoints.
- Packaged binaries and platform installers.
- Manual UI testing inside a running Wails app.
3. Methodology
I used a two-pass review:
- Attack-surface mapping of Wails API methods, import/export paths, file handling, DOM sinks, dependency/build chain, protocol clients, local proxy behavior, and credential flows.
- Defensive validation from source and tests, followed by smallest safe fixes with regression tests.
Tools/checks used:
- Static review with
rg, direct source reads, and line evidence. go test ./....- Focused tests for API, storage, export/import, validation, SSH client, and sessions.
npm audit --json.npm run build.go run golang.org/x/vuln/cmd/govulncheck@latest ./....bash -n build.sh release.sh.git diff --check.
4. Risk Rating Method
Severity considered attacker position, exploit preconditions, likely impact, and whether sensitive credentials or code execution could be affected.
- Critical: practical remote compromise or credential disclosure with minimal preconditions.
- High: credible dependency, credential, or supply-chain compromise.
- Medium: user-assisted or renderer/local attack that can affect confidentiality, integrity, or availability.
- Low: hardening, defense-in-depth, or intentionally insecure user-selected modes.
- Informational: verified area, non-exploitable issue, or test gap.
Confidence is High where source evidence and regression checks directly support the finding.
5. Attack Surface Map
Entry points:
- Wails-bound Go API methods in
internal/api/api.go. - Profile create/update/import/export and encrypted
.nexusimports. - Drag-and-drop file upload/paste in
frontend/src/js/terminal.js. - SSH, Telnet, RAW TCP, VNC, SFTP, GitHub API, and local shell sessions.
- Local VNC WebSocket proxy on loopback.
- Build/release automation and dependency installation.
Trust boundaries:
- Frontend renderer to Wails API.
- User-selected/imported local files to backend parsers.
- Saved/imported profiles to outbound network clients.
- Remote hosts and GitHub/GitHub Enterprise APIs to frontend rendering.
- Local filesystem/keychain/vault to application process.
- CI/release environment to dependency registries and Go module proxy.
Attacker starting positions considered:
- Malicious imported profile bundle.
- Crafted local file or filename dropped into the terminal.
- Compromised renderer or malicious frontend input reaching Wails APIs.
- Network attacker on SSH/VNC/GitHub paths.
- Dependency registry or release-time supply-chain manipulation.
6. Findings Summary Table
| ID | Severity | Confidence | Title | Component | Status |
|---|---|---|---|---|---|
| SEC-20260615-001 | Medium | High | Profile persistence and imports accepted unsafe or malformed profiles | API, validation, import | Fixed |
| SEC-20260615-002 | Medium | High | Import and drag-drop upload paths allowed memory exhaustion | Import/export, SFTP upload | Fixed |
| SEC-20260615-003 | Medium | High | Dropped filenames were rendered through innerHTML |
Frontend terminal file-drop modal | Fixed |
| SEC-20260615-004 | Medium | High | Unused SSH helper still defaulted to insecure host-key verification | SSH client | Fixed |
| SEC-20260615-005 | High | High | Dependency advisories affected esbuild and required x/net module versions |
Dependencies | Fixed |
| SEC-20260615-006 | Medium | High | Release automation installed unpinned build dependencies | CI/release supply chain | Fixed |
| SEC-20260615-007 | Low | High | Direct VNC remains unencrypted when SSH tunnelling is disabled | VNC client | Open |
7. Detailed Findings
Finding ID: SEC-20260615-001
Title
Profile persistence and imports accepted unsafe or malformed profiles
Severity
Medium
Confidence
High
Affected Files / Components
internal/api/api.go,CreateProfile,UpdateProfile,ImportProfiles,ImportProfilesEncrypted, lines 94-131, 715-728, 1176-1189, 1754-1791.internal/validation/validation.go, profile, GitHub, and VNC validation, lines 51-140 and 325-410.internal/storage/store.go,ImportProfiles, lines 365-380.
Description
The API comment said profile creation validates input, but the Wails API previously persisted profiles without calling the validation package. Plain JSON import also decoded into []*models.Profile and dereferenced entries without rejecting null. A malicious or corrupt import could therefore crash the app or store profiles with unsafe GitHub API base URLs that would later receive a saved or prompted token.
Evidence
The fixed code now calls validation.ValidateProfile before create/update and validates every imported profile before writing. GitHub API base URLs must be HTTPS unless loopback, must be absolute, and must not include URL credentials. Import strategy and null entries are rejected before persistence.
Regression tests were added in internal/api/api_test.go and internal/storage/storage_test.go.
Attack Scenario
A user imports a profile JSON containing a GitHub profile with http://evil.example/api/v3 as the API base URL, or a null profile entry. Before the fix, malformed data could be saved or crash import handling. If the malicious GitHub profile were later used, the app could send a token to an attacker-controlled endpoint.
Impact
Possible token exposure through malicious profile configuration, app crash during import, and fragile persisted state.
Recommendation
Validate all Wails-bound persistence inputs and imported profiles before storage. Reject non-HTTPS remote GitHub API URLs and malformed entries.
Safer Example
Implemented:
CreateProfileandUpdateProfilecallvalidation.ValidateProfile.ValidateGitHubConfigrejects remote HTTP and URL credentials.- Import helpers reject oversized, null, or invalid entries before writes.
Suggested Tests
Implemented:
- Unsafe GitHub base URL rejection.
- Null import entry rejection.
- Oversized JSON import rejection.
- Storage-level null import entry rejection.
- GitHub and VNC validation cases.
References
- CWE-20: Improper Input Validation.
- CWE-200: Exposure of Sensitive Information to an Unauthorized Actor.
- OWASP API Security Top 10: API3 Broken Object Property Level Authorization / unsafe object properties.
Assumptions / Limitations
GitHub Enterprise over non-loopback HTTP is now rejected. If a legitimate deployment needs HTTP, it should be placed behind local loopback or HTTPS.
Finding ID: SEC-20260615-002
Title
Import and drag-drop upload paths allowed memory exhaustion
Severity
Medium
Confidence
High
Affected Files / Components
internal/api/api.go, plain import and SFTP drag-drop upload, lines 715-718 and 1336-1350.internal/exportimport/exportimport.go, encrypted import file read, lines 37 and 131-144.frontend/src/js/terminal.js, drag-drop file upload cap, lines 10 and 829-849.
Description
Several local file paths loaded attacker-controlled or user-selected content fully into memory without a size cap. Plain profile JSON import accepted arbitrary string size from the renderer. Encrypted .nexus import used os.ReadFile on the selected path. Drag-drop SFTP upload read each browser File into an ArrayBuffer, converted it to base64, and sent it to the backend, where it was decoded into another byte slice.
Evidence
The fixed code adds:
- 5 MiB cap for plain profile JSON import.
- 10 MiB cap for encrypted
.nexusimport files. - 25 MiB frontend and backend caps for inline drag-drop SFTP uploads.
Regression tests cover oversized plain import, encrypted import, and backend upload payload rejection.
Attack Scenario
A user drops or imports a very large crafted file. Before the fix, the renderer and backend could allocate multiple full copies, causing high memory pressure or an app crash.
Impact
Local denial of service against the desktop application.
Recommendation
Keep bounded inline payload paths and prefer streaming APIs for larger transfers. The existing native SFTP upload path streams local files and should remain the recommended path for large uploads.
Safer Example
Implemented size checks before decode/read where practical:
base64.StdEncoding.DecodedLenguard before decoding drag-drop upload payloads.os.Statsize check before encrypted importos.ReadFile.
Suggested Tests
Implemented:
TestImportProfilesRejectsOversizedJSON.TestDecryptRejectsOversizedFile.TestSFTPUploadDataRejectsOversizedPayloadBeforeSessionLookup.
References
- CWE-400: Uncontrolled Resource Consumption.
- OWASP ASVS 12.4: file and resource limits.
Assumptions / Limitations
The caps are conservative application-level limits. Larger profile bundles or drag-drop uploads now require a streaming implementation or the existing native SFTP upload flow.
Finding ID: SEC-20260615-003
Title
Dropped filenames were rendered through innerHTML
Severity
Medium
Confidence
High
Affected Files / Components
frontend/src/js/terminal.js, file-drop modal, lines 720-742.
Description
The file-drop modal interpolated the dropped filename into an innerHTML template. Local files can have names containing HTML metacharacters. In a Wails desktop app, DOM script execution is higher impact than a normal static page because the renderer can call privileged Wails APIs.
Evidence
The fixed modal escapes the file-drop title with escapeHtml(title) before injecting it into the template.
Attack Scenario
A user downloads or receives a file with a crafted name such as an HTML tag with an event handler, then drops it into a terminal. Before the fix, the modal could parse the filename as markup.
Impact
Potential renderer-side script execution and access to Wails-bound APIs from malicious DOM content.
Recommendation
Do not put user-controlled strings into innerHTML. Use textContent or escape before insertion.
Safer Example
Implemented escapeHtml(title) in the file-drop modal.
Suggested Tests
Add a frontend interaction test that drops a file named with <img ...>-style metacharacters and asserts the modal text is rendered literally. Manual Wails/browser verification was not run in this audit.
References
- CWE-79: Improper Neutralization of Input During Web Page Generation.
- OWASP ASVS 5.3: output encoding.
Assumptions / Limitations
This fix covers the reviewed file-drop title sink. Other innerHTML templates were spot-checked; future work should add an automated DOM-sink lint or test pass.
Finding ID: SEC-20260615-004
Title
Unused SSH helper still defaulted to insecure host-key verification
Severity
Medium
Confidence
High
Affected Files / Components
internal/sshclient/sshclient.go,Connect, lines 72-75.internal/sshclient/sshclient_test.go, regression test, lines 12-19.
Description
Current production session code uses explicit host-key callbacks, and no remaining reviewed SSH/VNC path calls ssh.InsecureIgnoreHostKey. However, the internal sshclient.Connect helper still defaulted to an insecure host-key callback. That made future misuse easy.
Evidence
The helper now fails closed and instructs callers to use ConnectWithHostKeyCallback. rg found no remaining InsecureIgnoreHostKey references after the change.
Attack Scenario
A future feature calls the convenience method instead of ConnectWithHostKeyCallback. Before the fix, that new path would silently accept any SSH host key.
Impact
Potential SSH machine-in-the-middle exposure if the helper were reused.
Recommendation
Keep host-key policy explicit on every SSH path. Do not provide insecure convenience APIs in internal packages.
Safer Example
Implemented: Connect returns sshclient: host key callback is required.
Suggested Tests
Implemented TestConnectRequiresHostKeyCallback.
References
- CWE-295: Improper Certificate Validation, analogous to SSH host-key validation.
- OWASP ASVS 9.1.2: server authenticity validation.
Assumptions / Limitations
This is a preventive fix. The reviewed production paths already used explicit callbacks.
Finding ID: SEC-20260615-005
Title
Dependency advisories affected esbuild and required x/net module versions
Severity
High
Confidence
High
Affected Files / Components
frontend/package.json,esbuild, line 22.frontend/package-lock.json,esbuildand platform packages.go.mod,golang.org/x/net, line 15.go.sum.
Description
npm audit reported esbuild 0.28.0 affected by a high-severity advisory and a low-severity advisory. govulncheck -show verbose reported six vulnerabilities in required golang.org/x/net v0.54.0 modules, though no reachable symbols were called by this code.
Evidence
The fixed versions are:
esbuildbumped to^0.28.1.golang.org/x/netbumped tov0.55.0.
Post-fix npm audit --json reports zero vulnerabilities and govulncheck reports No vulnerabilities found.
Attack Scenario
For esbuild, a compromised or manipulated package installation environment could exploit known build-tool weaknesses. For x/net, future code changes could start calling vulnerable symbols if the module stayed on an affected version.
Impact
Supply-chain compromise risk during build/development and latent dependency risk.
Recommendation
Keep audited dependency patch versions current and include npm audit and govulncheck in regular release checks.
Safer Example
Implemented package/module upgrades and lockfile updates.
Suggested Tests
Implemented verification:
npm audit --json.go run golang.org/x/vuln/cmd/govulncheck@latest ./....go test ./....npm run build.
References
- CWE-494: Download of Code Without Integrity Check.
- CWE-1104: Use of Unmaintained Third Party Components.
- Go vulnerability database advisories for
golang.org/x/net. - GitHub Advisory Database for
esbuild.
Assumptions / Limitations
The x/net advisories were not reachable before the bump, according to govulncheck. The upgrade removes the latent module-level finding anyway.
Finding ID: SEC-20260615-006
Title
Release automation installed unpinned build dependencies
Severity
Medium
Confidence
High
Affected Files / Components
.github/workflows/release-builds.yml, Wails and npm install steps, lines 66-70.release.sh, Wails and npm install logic, lines 76-99.
Description
The release workflow installed the Wails CLI with @latest and used npm install rather than the lockfile-enforcing npm ci. Local release scripting also instructed or performed unpinned dependency installation in some paths.
Evidence
The fixed workflow installs the Wails CLI at the version resolved from go.mod and uses npm ci. release.sh now installs the pinned Wails CLI version from go.mod when needed and uses npm ci when frontend/package-lock.json exists.
Attack Scenario
A release build runs after a compromised or breaking upstream release, or after lockfile drift. Before the fix, release outputs could include unreviewed dependency changes.
Impact
Build reproducibility loss and supply-chain risk in release artifacts.
Recommendation
Use pinned module versions and lockfile-enforced installs in CI/release paths.
Safer Example
Implemented:
go install ...@$(go list -m -f '{{.Version}}' github.com/wailsapp/wails/v2).npm ciin CI and release script.
Suggested Tests
Implemented:
bash -n build.sh release.sh.- Frontend build after
npm ci.
References
- SLSA build integrity principles.
- CWE-494.
Assumptions / Limitations
The workflow still allows network dependency retrieval during CI. Stronger controls would include dependency caching, provenance, and artifact signing.
Finding ID: SEC-20260615-007
Title
Direct VNC remains unencrypted when SSH tunnelling is disabled
Severity
Low
Confidence
High
Affected Files / Components
internal/sessions/manager.go, direct VNC branch, lines 1012-1062.internal/vncclient/vncclient.go, direct TCP dial, lines 139-144.
Description
When VNC SSH tunnelling is disabled, the client makes a direct TCP VNC connection. Classic VNC deployments often provide weak or no transport encryption.
Evidence
The VNC client intentionally supports both SSH-tunnelled and direct modes. The direct branch uses net.DialTimeout to the VNC address.
Attack Scenario
A user connects directly to VNC over an untrusted network. A network attacker observes or tampers with remote desktop traffic, depending on the VNC server's negotiated security type.
Impact
Potential confidentiality and integrity loss for remote desktop sessions.
Recommendation
Keep direct VNC available for trusted networks, but clearly warn users when SSH tunnelling is disabled and consider requiring explicit confirmation for saved direct-VNC profiles.
Safer Example
Prefer enabling vnc.sshTunnel and verifying the SSH host key for the tunnel host.
Suggested Tests
Add a UI test that direct VNC profiles show a warning, and an integration test that tunnelled VNC requires a host-key callback.
References
- OWASP ASVS 9.1: secure channel requirements.
Assumptions / Limitations
This was not changed in this run because direct VNC can be a deliberate user choice for isolated networks.
8. Blackhat-Style Abuse Paths
Path A: malicious profile import to token exfiltration, now blocked.
- Provide a profile bundle with a GitHub API base URL pointing to attacker infrastructure.
- User imports and connects.
- App prompts for or retrieves a token and sends it to the configured endpoint.
- Defensive breakpoints now added: profile validation, HTTPS requirement for remote GitHub APIs, import validation before storage.
Path B: crafted dropped filename to renderer script execution, now blocked.
- User receives a file with HTML metacharacters in the filename.
- User drops it into a terminal.
- Filename is rendered in the file-drop modal.
- Defensive breakpoint now added: title is escaped before
innerHTMLinsertion.
Path C: oversized file to app memory exhaustion, now blocked for reviewed inline paths.
- User imports or drops an extremely large file.
- Renderer/backend allocate full file contents and base64 copies.
- App becomes slow or crashes.
- Defensive breakpoints now added: import and inline upload size caps.
Path D: direct VNC over hostile network, still open by design.
- User disables SSH tunnel and connects to a VNC server over an untrusted network.
- Network attacker observes/tampers with VNC traffic if protocol security is weak.
- Defensive breakpoint: use SSH tunnel; future UI warning recommended.
9. Denial-of-Service Review
Fixed DoS controls:
- Plain profile import now rejects payloads over 5 MiB.
- Encrypted profile import now rejects files over 10 MiB before
os.ReadFile. - Drag-drop SFTP upload now rejects files over 25 MiB in the frontend and backend before base64 decode.
- Null profile entries no longer panic import paths.
Checked and not found:
- SQL queries use parameters for profile and audit operations reviewed.
- GitHub repo fetch has timeouts and caps repository listing at 5000 repos.
- SSH/Telnet/RAW connection setup uses dial timeouts.
- VNC local proxy binds to loopback and uses a per-session token in the current code.
Residual DoS risks:
- RAW session history and terminal recordings can grow with long sessions.
- SFTP directory listing still depends on remote server behavior and could return very large directories.
- Direct VNC proxy bridges long-lived streams by design.
10. Dependency and Supply-Chain Review
Fixed:
esbuildupgraded to^0.28.1;npm audit --jsonnow reports zero vulnerabilities.golang.org/x/netupgraded tov0.55.0; govulncheck now reports no vulnerabilities.- Release workflow uses
npm ciand pinned Wails CLI version. release.shusesnpm ciwhen a lockfile exists and installs the Wails CLI at the version fromgo.mod.
Checked:
go.mod,go.sum,frontend/package.json,frontend/package-lock.json..github/workflows/release-builds.yml,build.sh,release.sh.
Notes:
- npm warns that
xtermpackages are deprecated in favor of the newer@xterm/*packages. That is not an active vulnerability fromnpm audit, but it is a maintenance risk.
11. Secrets and Sensitive Data Review
Checked:
- Repository search for private key markers, common cloud tokens, GitHub token prefixes, and obvious password assignments.
- Secret store uses AES-GCM with Argon2id-derived keys and writes vault data with
0600. - Profile export redacts inline SSH proxy/TOTP secrets when non-secret export mode is used.
No high-confidence committed secrets or private keys were found.
Residual notes:
- GitHub and remote connection tokens are still available to the running app process by design.
- Audit logging should continue to avoid logging raw secrets; reviewed audit messages do not include secret values.
12. Authentication and Authorisation Review
This is a desktop app, so there are no multi-user server authz roles in the reviewed code.
Checked/fixed:
- SSH host-key bypass helper removed by making
sshclient.Connectfail closed. - Current SSH and VNC tunnel paths use explicit host-key callbacks.
- GitHub API base URL validation now reduces token leakage to remote HTTP endpoints.
Residual:
- Users can still choose insecure protocols/modes such as direct VNC or legacy Telnet/RAW TCP.
13. Input Validation and Output Encoding Review
Fixed:
- API create/update now validates profiles before persistence.
- Plain and encrypted import validate every profile before storage.
- GitHub API URLs now require HTTPS except loopback and reject URL credentials.
- VNC validation supports VNC-specific host fallback and validates tunnel fields.
- File-drop filename is escaped before template insertion.
Checked:
- SSH key management already rejects path traversal key names with alphanumeric, hyphen, and underscore constraints.
- SFTP dropped filenames use
filepath.Basebefore remote path construction.
14. Frontend HTML/JavaScript Robustness Review
Fixed:
- File-drop modal filename sink now uses
escapeHtml. - Drag-drop upload rejects files over 25 MiB before FileReader allocation.
Checked:
- Notifications render messages with
textContent. - GitHub repository card fields reviewed use
textContentor escaping for remote metadata.
Not fully exercised:
- No live Wails UI/browser automation was run in this audit. Add frontend interaction tests for file-drop, import/export, and update UI in future work.
15. Infrastructure and Deployment Review
Fixed:
- GitHub Actions release workflow now uses pinned Wails CLI and
npm ci. - Local
release.shnow uses pinned Wails CLI andnpm ci.
Checked:
- No Docker, Kubernetes, Terraform, or cloud deployment config was present in the reviewed file list.
- Wails config was reviewed for frontend build/install commands.
Residual:
- Release artifacts are not signed/notarized in the reviewed workflow.
- CI workflow has
contents: write, required for release attachment, but should remain limited to release jobs.
16. Logging, Monitoring, and Error Handling Review
Checked:
- Audit log records profile changes, import/export counts, connects/disconnects, and secret access without raw secret values.
- Fixed import validation returns actionable errors rather than panics.
- Frontend upload errors now surface a user-safe oversized-file message.
Residual:
- Some protocol errors are emitted directly to the UI. Continue avoiding credentials in error strings from remote services.
- Consider adding structured debug logs for rejected imports without dumping payloads.
17. Test Coverage and Security Regression Gaps
Added/updated tests:
- API rejects unsafe GitHub base URL.
- API rejects null and oversized imports.
- API rejects oversized inline SFTP upload before session lookup.
- Export/import rejects oversized encrypted files.
- Storage import rejects null profile entries and invalid strategy before looping.
- Validation covers GitHub URL and VNC host/tunnel cases.
- SSH client
Connectrequires explicit host-key callback.
Remaining gaps:
- Browser/Wails interaction tests for file-drop XSS and upload cap.
- Load tests for very large SFTP directories, RAW history, and recordings.
- End-to-end import/export tests through the actual Wails UI.
- Release workflow provenance/signing tests.
18. Prioritised Remediation Plan
Immediate:
- Keep the fixed tests in CI.
- Add
npm auditand govulncheck to CI if not already present. - Warn users when saving or connecting direct VNC profiles without SSH tunnelling.
Short-term:
- Replace deprecated
xtermpackages with maintained@xterm/*packages. - Add frontend tests for DOM injection via filenames and imported profile metadata.
- Add streaming drag-drop upload support or route large drops through the native file upload path.
Medium-term:
- Add release artifact signing/notarization and checksums with provenance.
- Add application-level limits for RAW history, terminal recording size, SFTP listing size, and GitHub error body reads.
Long-term:
- Add a security-focused CI job: tests,
npm audit, govulncheck, secret scanning, and static DOM-sink checks. - Introduce a profile schema version and migration/normalization layer for imports.
19. False Positives / Needs Manual Verification
govulncheck -show verboseinitially reported module-levelx/netadvisories that were not reachable. The module was still upgraded to remove latent risk.- Direct VNC is intentionally supported; risk depends on network trust and VNC server security type.
- File-drop XSS was fixed by code inspection; manual Wails UI verification should still be added.
- No live external service testing was performed.
20. Final Notes
The repo is in a materially better state after this run: profile data is validated before storage, local resource-consumption paths are bounded, the file-drop DOM sink is escaped, dependency scanners are clean, and release dependency installation is more reproducible.
The main residual security posture issue is user-selected insecure connectivity: Telnet/RAW/direct VNC remain useful but should be presented with clear safety context.