Every distributed application needs four independent security layers:
Layer
Function
Failure Mode
1. Authentication
Verify identity (OAuth, MSAL, API keys)
Unauthorized access
2. Authorization
Role-based access control (RBAC)
Privilege escalation
3. Secrets Scanning
Detect leaked credentials in source
Data breach
4. Audit Logging
Record all security-relevant events
Undetected compromise
Rule: Each layer must work independently. A failure in Layer 1 should not cascade — Layer 2 still blocks unauthorized actions, Layer 3 still catches leaked keys, Layer 4 still records the attempt.
PII Protection
3-Layer Exclusion Model
For projects that package and distribute source files:
Layer
Implementation
Catches
1. .gitignore
Exclude from version control
Personal config, local data
2. .vscodeignore / build exclusions
Exclude from package
Dev-only files, test data
3. Build pipeline scan
Regex validation gate
Anything layers 1-2 missed
Personal Data Rules
Location
Allowed?
Alternative
Source code headers
No
Use team/org name
package.json author
Org name only
"author": "Team Name"
README credits
Generic unless opted in
"Created by the team"
Error messages
No PII ever
Use error codes
Telemetry
No PII without consent
Anonymized identifiers
Rationale: Users installing software see package.json and source headers. Personal names create trust concerns and privacy issues.
Secrets Scanning
Detection Patterns
Pattern
Regex
Severity
API keys
/[A-Za-z0-9_\-]{32,}/
High
Connection strings
/Server=.*;[P]assword=.*/i
Critical
JWT tokens
/eyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+/
Critical
Private keys
/-----BEGIN.*PRIVATE KEY-----/
Critical
Azure keys
/[A-Za-z0-9+/]{86}==/
High
False Positive Reduction
Naive regex scanning floods developers with noise. Filter these contexts:
Context
Example
Action
Import statements
import { KEY_LENGTH } from...
Skip
Comments explaining patterns
// API keys look like: abc123...
Skip
Env variable definitions
const API_KEY = process.env.KEY
Skip (value is reference)
Test fixtures with dummy data
const testKey = "test-key-1234"
Skip if in test folder
Actual hardcoded secrets
const key = "sk-live-abc123..."
ALERT
Rule: Only alert on literal string values that match secret patterns outside of comments, imports, and environment variable references.
Priority Matrix for Findings
When security audits surface findings, triage with this matrix:
Priority
Criteria
SLA
P0
Active secret exposure, data breach
Immediate (hours)
P1
Vulnerability in auth/authz layer
24 hours
P2
Missing security control (no CSP, etc.)
Sprint
P3
Best practice gap (logging format, etc.)
Backlog
Permission Minimization
The Less-Is-More Principle
Request ONLY the permissions your software actually uses:
Anti-pattern
Problem
Fix
Request Mail.Send for reading contacts
Users fear email spam
Request Contacts.Read only
Request admin scopes "for future use"
Over-privileged from day one
Request when feature ships
Broad * scopes
No granular control
Request specific sub-scopes
Rule: Users evaluate trust based on the scariest permission requested. One unnecessary permission can tank adoption.
Permission Audit Checklist
For every capability that requires a permission:
Is this the minimum scope that enables the feature?
Can this be a delegated permission (user context) instead of application?
Can this be requested just-in-time instead of at install?
Does the permission description match what users expect?
Can we function without this permission (graceful degradation)?
Content Security Policy (CSP)
Secure UI Patterns
For WebViews, panels, and embedded web content:
Anti-pattern
Problem
Secure Alternative
onclick="handler()"
Inline scripts violate CSP
data-cmd attribute + delegated listener
eval()
Code injection vector
Pre-compiled templates
innerHTML = userInput
XSS vulnerability
textContent or sanitized HTML
<script src="cdn">
External dependency risk
Bundle locally
Data-Cmd Pattern
Replace inline event handlers with data attributes:
WebViews run in sandboxed iframes. Communication must use message passing:
Direction
Method
Example
Extension → WebView
webview.postMessage(data)
Send state updates
WebView → Extension
vscode.postMessage(data)
Report user actions
WebView → External URL
BLOCKED
window.open() silently fails
WebView → Local files
Via extension only
Request through message
Critical: window.open() silently fails in WebViews. Links that need to open externally must send a message to the extension, which calls vscode.env.openExternal().