Security Boundaries In Modern Systems
Security is an architecture problem, not a library problem. Assume everything outside your system is hostile. Defense in depth, zero trust, and proper auth patterns keep you safe when, not if, you're attacked.
Security is an architecture problem, not a library problem. You can't add security with a library at the end. It must be baked into how you think about systems.
The principle: assume everything outside your system is hostile. Every input is malicious. Every user is compromised. Every network request could be intercepted. Design defensively.
Defense in Depth
Don't rely on a single security layer. If one layer is breached, others are still in place.
Layers:
- Network: Firewalls, VPCs, network segmentation. Traffic can't reach your database without going through your application.
- Application: Authentication, authorization, input validation, rate limiting.
- Data: Encryption at rest and in transit. Secrets management.
- Operational: Least privilege, audit logging, access controls.
A breach in one layer doesn't compromise everything. A attacker who gets past your application firewall still can't access the database (network layer). An attacker who compromises an engineer's laptop still can't read encrypted data (data layer).
Authentication vs. Authorization
These are different problems.
Authentication: Is the user who they say they are? Login. Verify a password or token. Confirm identity.
Authorization: What is the authenticated user allowed to do? Can they read this file? Can they delete this object? What permissions do they have?
Authentication Patterns
Session-Based: User logs in. Server creates a session. Session ID is stored in a cookie. Client sends the cookie with each request.
Pros: Simple. Server controls revocation (delete the session). Cons: Server must store sessions (memory or database). Doesn't scale well to many servers (sessions aren't shared).
Token-Based (JWT): User logs in. Server creates a signed token containing claims (user ID, permissions, expiration).
{
"sub": "user-123",
"name": "Alice",
"exp": 1678876800,
"iat": 1678790400
}Client stores the token (localStorage, secure cookie). Sends it with each request in the Authorization header.
Pros: Stateless (no server storage). Scales well. Works across domains (good for microservices). Cons: Token revocation is hard (you can't delete a token). Token size increases with claims.
OAuth 2.0: Delegate authentication to a third party (Google, GitHub). You don't store passwords. The third party does.
User clicks "Login with Google." Redirects to Google. User logs in. Google redirects back with a token. Your app uses the token.
Pros: Don't handle passwords (don't store them, can't leak them). User experience (one click). Cons: Dependency on third party. If Google is down, login is down.
Authorization Patterns
RBAC (Role-Based Access Control): Users have roles (admin, moderator, user). Roles have permissions (read, write, delete).
user = User(id=123, role="moderator")
if user.role == "admin":
allow_delete()Simple to understand. Problem: roles are coarse. "Moderator" might be too permissive for some users or too restrictive for others.
ABAC (Attribute-Based Access Control): Decisions are based on attributes of the user, resource, and context.
can_delete = (
user.role == "admin"
and resource.owner_id == user.id
and time.hour() < 22 # Can't delete after 10 PM
)More flexible. Problem: complex rules become hard to understand and maintain.
ACL (Access Control List): The resource specifies who can access it.
document.permissions = {
"user-123": ["read", "write"],
"user-456": ["read"],
}Granular. Problem: checking permissions on every access is expensive.
Combination: Most systems use a mix. RBAC for broad permissions. ACL for specific resources. Context for sensitive operations.
API Security
APIs are attack surfaces.
Input Validation: Never trust user input. Validate everything.
# Bad
user_id = request.args.get('id')
user = db.get_user(user_id) # SQL injection risk
# Good
user_id = int(request.args.get('id')) # Parse to int (rejects non-numeric)
user = db.get_user(user_id) # Safe
# Better
user_id = request.args.get('id')
if not isinstance(user_id, int) or user_id < 0:
return 400, "Invalid user ID"
user = db.get_user(user_id)Rate Limiting: Attackers often use brute force (trying millions of passwords) or DDoS (sending millions of requests). Rate limit by IP, by user, by API key.
@rate_limit(requests=100, window=3600) # 100 requests per hour
def login():
passHTTPS/TLS: Always use HTTPS. TLS encrypts data in transit. Without it, credentials and data are visible to network eavesdroppers.
CORS (Cross-Origin Resource Sharing): Browsers block requests to different domains (cross-origin). CORS controls which domains can access your API.
Access-Control-Allow-Origin: https://example.comThis header says "example.com can make requests to this API." Without it, browsers block the request.
CSRF (Cross-Site Request Forgery): An attacker tricks you into making a request. You're logged into your bank. You visit attacker.com. The page contains <img src="https://bank.com/transfer?to=attacker&amount=1000">. Your browser sends the request with your bank cookies. The transfer happens.
Prevent with CSRF tokens. The server includes a unique token in forms. The form submission must include the token. Attacker can't forge the token.
<form action="/transfer" method="POST">
<input type="hidden" name="csrf_token" value="abc123xyz">
<input type="number" name="amount">
<button>Transfer</button>
</form>XSS (Cross-Site Scripting): Attacker injects JavaScript. A comment includes <script>steal_cookies()</script>. When other users view the comment, the script runs and steals their cookies.
Prevent by:
- Escaping HTML.
<script>becomes<script> - Using templating engines that escape by default
- Content Security Policy (CSP)
CSP (Content Security Policy): A policy that tells the browser what content is allowed.
Content-Security-Policy: script-src 'self' https://trusted-cdn.comThis says "JavaScript can only come from this domain or https://trusted-cdn.com." Inline scripts are blocked.
Secrets Management
Passwords, API keys, database credentials. These are secrets. They must be protected.
Don't commit secrets to git. An engineer commits a password to the repository. The repository is pushed to GitHub. An attacker clones the repo and finds the credentials.
Use secrets management:
- Environment variables
- Secrets vaults (HashiCorp Vault, AWS Secrets Manager, 1Password)
- Secrets in CI/CD runners (GitHub Actions, GitLab CI)
# Bad
DB_PASSWORD = "my-password" # Don't do this
# Good
import os
DB_PASSWORD = os.environ['DB_PASSWORD'] # From environment
# Better
import hvac
vault = hvac.Client(url='http://vault-server')
secrets = vault.secrets.kv.read_secret_version(path='db-credentials')
DB_PASSWORD = secrets['data']['data']['password']Zero Trust Architecture
Traditional security assumes the network perimeter is secure. Inside the network, trust everything.
Zero trust assumes nothing is trusted. Every request, every service, every device must authenticate and authorize. Even if you're on the corporate network, you're not automatically trusted.
Principles:
- Verify every request: Every API call, even internal ones, requires authentication.
- Least privilege: Users and services have the minimum permissions needed.
- Microsegmentation: Network is divided into small segments. Breach of one segment doesn't compromise others.
- Logging and monitoring: Every access is logged and monitored for anomalies.
In a zero trust system, an engineer's compromised laptop doesn't give the attacker access to the database. The engineer's credentials would be used, but the system logs unusual access patterns and blocks it.
Cryptography Basics
Not all cryptography is equal.
Symmetric encryption: Same key encrypts and decrypts. Fast. Problem: how do you share the key?
encrypt("Hello", key="secret") = "xyzabc"
decrypt("xyzabc", key="secret") = "Hello"Asymmetric encryption: Public key and private key. Public key encrypts. Private key decrypts. Problem: slower than symmetric.
encrypt("Hello", public_key) = "xyzabc"
decrypt("xyzabc", private_key) = "Hello"Good for key exchange. You publish your public key. Anyone can encrypt a message. Only you (with the private key) can decrypt.
Hashing: One-way function. Hash a password. Can't reverse the hash to get the password. But you can hash again and compare.
hash("password123") = "abc123xyz"
hash("password123") = "abc123xyz" # Hashes match, password is correct
hash("wrong") = "different" # Doesn't matchUse proper hashing for passwords (bcrypt, scrypt, Argon2). Don't use fast hashes (MD5, SHA-1). Attackers can brute-force them.
mTLS (Mutual TLS)
TLS encrypts communication between client and server. Mutual TLS also authenticates both sides.
Service A sends a certificate. Service B verifies it. Service B sends a certificate. Service A verifies it. Both sides are authenticated.
Good for:
- Internal services (service-to-service)
- Sensitive operations
- Zero trust architecture
Harder to operate (certificate management is complex).
Principle of Least Privilege
Every user, service, and process has the minimum permissions needed to do their job.
A read-only database user can't delete. A service that only fetches data doesn't have write access. An engineer has access to production logs but not production databases.
Violations:
- Giving everyone admin access
- Not removing access when engineers leave
- Services with more permissions than they need
Least privilege is operationally complex (more rules to manage). But it reduces blast radius. A compromised service can't do more damage than its permissions allow.
Compliance and Audit
Security isn't just technical. Audit logs and compliance matter.
Audit Logging: Every access is logged. Who accessed what, when, from where.
2024-03-05 10:30:45 user-123 read document-456 from 192.168.1.100 SUCCESS
2024-03-05 10:31:12 user-789 write document-456 from 192.168.1.101 DENIEDCompliance Standards:
- SOC 2: Security, availability, integrity, confidentiality
- GDPR: Data privacy (especially relevant in Europe)
- HIPAA: Health data privacy
- PCI DSS: Payment card data security
These standards require:
- Data encryption
- Access controls
- Audit logging
- Incident response plans
AI-Generated Code and Security
Code generators often ignore security. Generated code might be vulnerable to SQL injection, XSS, CSRF. Generators don't think about least privilege or defense in depth.
Bitloops helps by generating secure code by default. Input validation is built in. SQL queries are parameterized. Authentication and authorization are explicit.
Frequently Asked Questions
Should I use JWT or sessions?
JWT for stateless, scalable systems. Sessions for simple systems where server-side state is fine. JWT is harder to revoke. Use refresh tokens to handle revocation.
What's the difference between authentication and authorization?
Authentication: who are you? (login) Authorization: what are you allowed to do? (permissions)
Should I store passwords?
Never store plain passwords. Hash them with bcrypt, scrypt, or Argon2. Even better, use OAuth/OIDC and don't store passwords at all.
Is HTTPS always necessary?
Yes. Use HTTPS everywhere. Self-signed certificates are fine for internal services. CA-signed certificates for public APIs.
How do I handle API key revocation?
Store API keys in a database. To revoke, mark the key as inactive. To verify, check the database on each request. (Slower than JWT, but supports revocation.)
What's the difference between RBAC and ABAC?
RBAC: user has a role, role has permissions. ABAC: permissions depend on user attributes, resource attributes, and context. ABAC is more flexible but complex.
Primary Sources
- OWASP's authoritative guide to top security vulnerabilities and defenses. OWASP Top 10
- PortSwigger's comprehensive web security academy and training resource. Web Security Academy
- OAuth 2.0 authorization framework specification and best practices. OAuth 2.0 RFC 6749
- JSON Web Tokens specification for secure information transmission. JWT RFC 7519
- TLS 1.3 protocol specification for encryption and secure communication. TLS 1.3 RFC 8446
- NIST zero trust architecture guide for modern security boundaries. Zero Trust Architecture
- Ross Anderson's comprehensive guide to security engineering principles. Security Engineering
More in this hub
Security Boundaries In Modern Systems
9 / 10Get Started with Bitloops.
Apply what you learn in these hubs to real AI-assisted delivery workflows with shared context, traceable reasoning, and architecture-aware engineering practices.
curl -sSL https://bitloops.com/install.sh | bash