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