Because Security is Not a “Non-Functional Requirement”
Related Article: BDD-style Acceptance Testing

Too often we treat security as a nice-to-have; a check box buried deep in a backlog, languishing behind feature flags and performance tickets. But when data is leaked, when credentials are exposed, security suddenly becomes painfully real. All at once, the consequences land squarely in your inbox, your boardroom — and the public square. That’s when “Wish You Were Here?” isn’t a postcard caption — it’s a solemn announcement.
We have tools today — Static Application Security Testing (SAST), Dynamic Application Security Testing (DAST), dependency scanners, secret detection, fuzzers, and more. They’re powerful, but they can become rigid gatekeepers if they’re not an integral part of the continuous integration pipeline.
In my book The Effective Software Engineer, I emphasize something that often gets lost:
CI is behavior, not a button you install.
A healthy CI pipeline isn’t just a server and a report; it’s a culture of continuous feedback and responsibility. And while culture is human, the tools must be technical and automated.
🛠️ You should absolutely embed SAST and DAST into your technical pipeline — not as an afterthought, not as a separate sprint task, but as standard quality feedback that developers see before code is merged.
Here’s an example of what that looks like in practice: 🔗 https://github.com/my-ellersdorfer-at/my-stuff
If security is treated only as a “non-functional requirement,” then we will experience it most acutely when it fails. Instead, let’s bake secure thinking into how we build, test, and deliver software — every commit, every integration, every day.
What is SAST?
Static Application Security Testing (SAST) analyzes your source code without executing it. It looks for security vulnerabilities, reliability issues, and maintainability problems such as:
- Injection risks
- Insecure cryptography usage
- Broken error handling
- Code paths that lead to undefined or unsafe behavior
SAST shines early in the development lifecycle. It gives fast feedback while code is still cheap to change — ideally before it ever reaches main.
--
SAST in the book’s example project
In the example project accompanying The Effective Software Engineer, SAST is part of the commit stage of the CI pipeline, not an optional scan.
- Executed automatically on every push
- Combined with unit and integration tests
- Enforced via a quality gate
This is implemented using Sonar analysis directly in CI.

The important part is not the tool itself, but where it runs: Security feedback arrives at the same time as test failures — while the developer is still context-loaded.
What is DAST?
Dynamic Application Security Testing (DAST) analyzes a running system. Instead of inspecting code, it behaves like an attacker and probes the application from the outside.
DAST is especially effective at finding:
- Misconfigurations
- Authentication and authorization flaws
- Missing security headers
- Runtime exposure issues that static analysis cannot see
DAST answers a different question than SAST:
“Given what we built and deployed — how does it behave under attack?”
DAST in the book’s example project
In the book’s example project, DAST is implemented as a scheduled CI workflow, not a manual penetration test and not a one-off audit.
- The application is built and started automatically
- Supporting infrastructure (e.g. identity provider) is provisioned
- A baseline OWASP ZAP scan is executed against the running system
- Reports are archived as CI artifacts
This setup is fully automated using GitHub Actions and OWASP ZAP
name: DAST - OWASP ZAP against local app (daily)
on:
schedule:
- cron: "0 1 * * *" # daily 01:00 UTC
workflow_dispatch: {}
jobs:
zap_dast:
runs-on: ubuntu-latest
env:
BOOT_MODULE: "application"
APP_URL: "http://localhost:8888"
KEYSTORE_DIR: "${{ github.workspace }}/keystore"
KEYSTORE_PASSWORD: "changeit"
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Java 25
uses: actions/setup-java@v4
with:
distribution: "temurin"
java-version: "25"
cache: "maven"
- name: (Optional) Show realm file exists
run: |
ls -la acceptance/src/test/resources/keycloak/my-stuff.realm.json
- name: Maven build + acceptance tests
run: mvn -B clean verify --file pom.xml -Dgroups=acceptanceTest
- name: Start Keycloak via docker compose
run: docker compose -f docker-compose.yaml up -d
- name: Wait for Keycloak realm (OIDC discovery)
run: |
set -e
URL="https://localhost:8443/realms/my-stuff/.well-known/openid-configuration"
for i in {1..120}; do
if curl -kfsS "$URL" >/dev/null; then
echo "Realm is imported and OIDC discovery is reachable."
exit 0
fi
sleep 2
done
echo "Realm import not ready in time."
docker compose -f docker-compose.yaml logs --no-color keycloak | tail -n 300 || true
exit 1
- name: Package Spring Boot app (reactor build)
run: mvn -B -DskipTests -pl "${BOOT_MODULE}" -am package
- name: Start Spring Boot (background, java -jar)
run: |
set -e
JAR_PATH="$(ls -1 ${BOOT_MODULE}/target/*.jar | grep -v 'original-' | head -n 1)"
echo "Starting ${JAR_PATH}"
nohup java \
-Djavax.net.ssl.keyStore=${KEYSTORE_DIR}/cacerts \
-Djavax.net.ssl.keyStorePassword=${KEYSTORE_PASSWORD} \
-Djavax.net.ssl.trustStore=${KEYSTORE_DIR}/cacerts \
-Djavax.net.ssl.trustStorePassword=${KEYSTORE_PASSWORD} \
-jar "${JAR_PATH}" \
--server.port=8888 \
> app.log 2>&1 &
- name: Wait for app to be ready
run: |
set -e
echo "Waiting for ${APP_URL} ..."
for i in {1..120}; do
if curl -fsS "${APP_URL}" >/dev/null; then
echo "App is up."
exit 0
fi
sleep 1
done
echo "App did not become ready in time."
echo "---- app.log (tail) ----"
tail -n 250 app.log || true
exit 1
- name: OWASP ZAP Baseline Scan
run: |
mkdir -p zap-reports
ZAP_UID="$(docker run --rm zaproxy/zap-stable id -u)"
ZAP_GID="$(docker run --rm zaproxy/zap-stable id -g)"
sudo chown -R "${ZAP_UID}:${ZAP_GID}" zap-reports
docker run --rm --network host \
-v "${PWD}/zap-reports:/zap/wrk:rw" \
zaproxy/zap-stable \
zap-baseline.py \
-t "${APP_URL}" \
-r zap-baseline-report.html \
-J zap-baseline-report.json \
-w zap-baseline-warn.md \
|| true
- name: Upload ZAP reports
uses: actions/upload-artifact@v4
with:
name: zap-reports
path: zap-reports
- name: Upload app log
if: always()
uses: actions/upload-artifact@v4
with:
name: app-log
path: app.log
if-no-files-found: ignore
The key idea: DAST is repeatable, visible, and boring — exactly what you want from security checks.
Why this matters for Continuous Integration
Security does not fail because teams lack tools. It fails because feedback arrives too late, too detached, or outside the normal development flow.
When SAST and DAST are:
- Part of CI
- Run automatically
- Visible to developers
- Treated like tests
…security stops being an abstract requirement and becomes daily engineering behavior.
And that is the core message of The Effective Software Engineer:
Sustainable software quality — including security — emerges from habits, feedback loops, and responsibility, not from documents or checklists.
How this connects to The Effective Software Engineer
This article directly builds on Chapter 4 — Test Driven Development and Chapter 5 — Useful goals for software development teams in The Effective Software Engineer.
In those chapters, I argue that:
- Quality is not something we “add” at the end of development
- Fast feedback is the primary economic driver of sustainable software
- Continuous Integration is a behavioral system, not a tooling checklist
SAST and DAST fit naturally into this model. They are not “security phases” or specialized audits — they are feedback mechanisms, no different in principle from unit tests, acceptance tests, or build verification.
In the book’s example project, you’ll see this philosophy applied consistently:
- SAST runs in the commit stage, alongside tests, enforcing security and maintainability as first-class quality signals
- DAST runs against a fully started system, exercising real authentication, real infrastructure, and real attack surfaces
- Both are automated, repeatable, and visible to the team — not delegated to a separate role or postponed to “later”
If this article resonates with you, the book goes deeper into why these practices work, how they shape team behavior, and what organizational conditions are required so that security, quality, and delivery speed reinforce each other instead of competing.
Related Article: BDD-style Acceptance Testing