Skip to content

node_04 - Node.js vs Browser JavaScript

Same language , completely different runtime capabilities
This is the single most important concept for security people to internalize. JavaScript in the browser has training wheels - no filesystem access , no raw sockets , no process spawning , no arbitrary code loading. JavaScript in Node has none of those restrictions. The same syntax , the same function keyword , the same Array() - but what you can DO with that code is fundamentally different

what's missing in Node

The browser provides a set of APIs that Node deliberately omits because they make no sense on a server:

// These exist in the browser but NOT in Node
// DOM APIs
document.getElementById('root')          // ReferenceError
document.querySelector('div')            // ReferenceError

// Window APIs
window.addEventListener('load' , fn)    // ReferenceError
window.location.href                     // ReferenceError
window.localStorage.setItem('k' , 'v')  // ReferenceError

// Browser-specific APIs
history.back()                           // ReferenceError
navigator.userAgent                      // ReferenceError
XMLHttpRequest                           // ReferenceError (use http module instead)

what Node adds

Instead of browser APIs , Node gives you access to the operating system:

// Filesystem - read , write , delete files
const fs = require('fs')
const data = fs.readFileSync('/etc/hostname' , 'utf8')

// Network - raw TCP/UDP sockets
const net = require('net')
const client = net.createConnection({ port: 8080 } , () => {
  client.write('GET / HTTP/1.1\r\n\r\n')
})

// Process control - spawn child processes
const { exec } = require('child_process')
exec('ls -la' , (err , stdout) => console.log(stdout))

// OS information
const os = require('os')
console.log(os.cpus())     // CPU info
console.log(os.freemem())  // Available memory
console.log(os.networkInterfaces()) // Network adapters

the global objects compared

Browser Node Purpose
window global Global object
document - DOM access (not in Node)
fetch fetch (Node 18+) or http HTTP requests
localStorage fs or database Persistent storage
location __dirname , __filename Current path
process.env process.env Environment (browser polyfills this)
// In Node
console.log(global === globalThis) // true

// In Browser
console.log(window === globalThis) // true

// globalThis works in both environments - use it for cross-platform code

module systems

Browsers use ES Modules (import/export). Node supports both CommonJS (require/module.exports) and ES Modules:

// CommonJS - Node's original module system (synchronous)
const fs = require('fs')
module.exports = { myFunction }

// ES Modules - modern , standard (asynchronous)
import fs from 'fs'
export const myFunction = () => {}

The critical difference: CommonJS loads modules synchronously (fine for server startup) while ES Modules are asynchronous (required for browsers). Node supports both but you can't mix them in the same file without special configuration

the biggest security difference

Browser JavaScript IS sandboxed. Node JavaScript IS NOT
This isn't a subtle distinction - it's the core security property that determines what attacks are possible

In the browser: - XSS can steal cookies from document.cookie - CSRF can forge requests - Clickjacking can trick users - The attacker is constrained by Same-Origin Policy , CSP , and the browser security model - They CANNOT read files , scan the internal network , or install backdoors

In Node: - RCE can read any file the process user can read - SSRF can scan internal networks and hit cloud metadata endpoints - Remote code execution means full system access - The attacker inherits the process user's permissions

// Identical syntax , completely different impact

// Browser: this is a cookie stealer (bad but contained)
document.cookie  // only affects this origin, sandboxed by browser

// Node: this is a server compromise (your whole infra is burning)
require('fs').readFileSync('/etc/kubernetes/admin.conf')
// Now the attacker has your Kubernetes admin config
// They can deploy containers , steal secrets , pivot to other services

the vm module lie

Node has a vm module that looks like it creates a sandboxed environment for running untrusted code. It does not. The documentation literally says it's not a security boundary

const vm = require('vm')

// This LOOKS like a sandbox
const code = 'console.log("running in sandbox")'
vm.runInNewContext(code , { console })

// But it's NOT secure - sandbox escapes exist
try {
  vm.runInNewContext(`
    const process = this.constructor.constructor('return process')()
    process.env.SECRET
  ` , {})
  // Congratulations , the "sandboxed" code just read your env vars
} catch (e) {
  console.log('Sandbox escape failed (this time)')
}

Every few months someone publishes a vm sandbox escape , gets a CVE , and the Node team says "we told you it's not a security boundary." If you need to run untrusted code , use a container or a separate process with restricted permissions

when to pick Node vs browser for a task

Task Better in Why
DOM manipulation Browser Node has no DOM
File processing Node Browser needs user gesture + file input
CPU-heavy compute Neither Use worker threads (Node) or Web Workers (browser)
Real-time communication Both WebSockets work everywhere
Database access Node Browser can't connect directly to databases
CLI tools Node Browser can't access the terminal

The fundamental rule: Node is for backend services , build tools , and automation. The browser is for user interfaces. Don't use Node where rendering matters. Don't use the browser where filesystem access is required

prerequisites

node_03 - JavaScript Requirements


next -> node_05_cmd_line.md