Skip to main content

05. Application Security Architecture

1. The Concept (ELI5)

Imagine you are running a restaurant. Application Security Architecture is like the health inspector's rulebook combined with the kitchen's layout.

If the raw chicken (untrusted user input) is kept right next to the fresh salad (database execution), people will get sick (SQL Injection). If the recipes (source code) contain the safe combination to the vault (hardcoded secrets), an employee might steal the money.

Assessing AppSec architecture means looking at how the software is built, how it handles data, and ensuring that security checks (like washing hands and cooking meat to temp) are baked into the assembly line (the CI/CD pipeline). This is called DevSecOps.

2. The Visual

A secure software development lifecycle (SDLC) incorporates automated checks at every stage.

3. The Code

A core architectural flaw is failing to parameterize database queries, leading to SQL Injection.

Go

Vulnerable Code: String Concatenation

import "database/sql"

func getUser(db *sql.DB, username string) {
// DO NOT DO THIS. Raw input is concatenated into the query.
query := "SELECT * FROM users WHERE username = '" + username + "'"
rows, _ := db.Query(query)
// process rows...
}

Production-Ready Secure Code: Parameterized Queries

import "database/sql"

func getUser(db *sql.DB, username string) {
// The database driver safely handles the substitution, preventing injection
query := "SELECT * FROM users WHERE username = $1"
rows, err := db.Query(query, username)
if err != nil {
// handle error
}
// process rows...
}

Python (SQLAlchemy)

Vulnerable Code: Raw Execution

from sqlalchemy import text

def get_user(db_session, username):
# Executing raw SQL with user input
query = text(f"SELECT * FROM users WHERE username = '{username}'")
result = db_session.execute(query)
return result.fetchall()

Production-Ready Secure Code: ORM Abstraction

from models import User

def get_user(db_session, username):
# Utilizing the ORM safely parameterizes the query in the background
user = db_session.query(User).filter(User.username == username).first()
return user

TypeScript (Prisma)

Vulnerable Code: Raw Query Injection

import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()

async function getUser(username: string) {
// Prisma allows raw queries, which can be vulnerable if abused
const users = await prisma.$queryRawUnsafe(`SELECT * FROM User WHERE name = '${username}'`)
return users
}

Production-Ready Secure Code: Type-Safe ORM

import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()

async function getUser(username: string) {
// Secure by design, Prisma handles the parameterization
const user = await prisma.user.findUnique({
where: {
name: username,
},
})
return user
}

4. The Guardrail

To enforce Application Security Architecture, we implement CI/CD rules that break the build if a vulnerability is detected.

Semgrep Rule (Detect SQL Injection in Node.js)

rules:
- id: node-sql-injection
patterns:
- pattern: |
$DB.query(..., <... $REQ.$QUERY ...>, ...)
- pattern-not: |
$DB.query("...", [$REQ.$QUERY], ...)
message: "Potential SQL Injection detected. Use parameterized queries."
languages:
- javascript
- typescript
severity: ERROR

GitHub Actions (Enforce SAST)

name: DevSecOps Pipeline
on: [push, pull_request]

jobs:
security-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Run Semgrep
uses: returntocorp/semgrep-action@v1
with:
config: "p/default"
- name: Run Dependency Check (SCA)
uses: snyk/actions/node@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}