← DevSecOps← DevSecOps
DevSecOpsDevSecOps19 Th7, 2026Jul 19, 202619 phút đọc16 min read

Nền tảng lập trình & ScriptingProgramming & Scripting Foundations

Thuộc bộ kiến thức DevSecOps Roadmap.

Tổng quan

DevSecOps không phải là vai trò “đứng ngoài xem”. Các security engineer chỉ biết đọc dashboard và tạo ticket sẽ bị giới hạn trong những check mà người khác đã xây sẵn. Ngay khi bạn có thể viết một script, bạn có thể tự động hóa việc scan hàng nghìn host thay vì click từng cái một, parse output lộn xộn của một tool thành thứ mà pipeline có thể dùng để gate, nối hai tool vốn không được thiết kế để nói chuyện với nhau, hoặc triage một incident bằng cách grep log nhanh hơn bất kỳ giao diện đồ họa nào.

Note này bao quát các ngôn ngữ quan trọng nhất trong công việc DevSecOps hằng ngày — mỗi ngôn ngữ mạnh ở điểm nào, một ví dụ runnable cho từng ngôn ngữ, và một bảng so sánh giúp bạn chọn đúng công cụ cho đúng việc. Mục tiêu không phải là thành thạo tuyệt đối một ngôn ngữ nào, mà là đủ fluency ở mỗi ngôn ngữ để đọc được tooling của người khác, tự viết các utility nhỏ, và biết nên dùng ngôn ngữ nào cho tình huống nào. Note này tiếp nối Introduction to DevSecOps, vốn bao quát phần culture và process của discipline này, còn note này cung cấp bộ công cụ kỹ thuật tương ứng.

Vì sao điều này quan trọng riêng cho công việc security

Kiến thức nền tảng

Bảng so sánh ngôn ngữ ở mức tổng quan

Ngôn ngữUse case security điển hìnhVí dụ tool viết bằng ngôn ngữ này
BashNối các CLI tool lại, loop qua host, cron job, check nhanh khi incident responseNhiều script hardening/audit trên Linux (ví dụ lynis dùng shell rất nhiều)
PythonAutomation tổng quát, script gọi API, helper tooling cho SAST/DAST, exploit PoCbandit (SAST cho Python), sqlmap, các tool dựa trên Scapy
GoScanner chạy concurrent, security CLI tool portable phân phối dưới dạng single binarynuclei, trivy, gitleaks, subfinder
PowerShellAudit/hardening Windows và Active Directory, offensive AD toolingPowerView, PowerSploit, các script security baseline của chính Microsoft
RubyPhát triển exploit và module trong một framework có sẵnMetasploit Framework
RustFuzzer và parser memory-safe cho input không tin cậy, CLI tool hiệu năng caocargo-fuzz, RustScan
JavaScript/Node.jsTooling security cho web app, headless browser automation, check trong pipeline JSScanner dựa trên Puppeteer/Playwright, ESLint security plugin
C/C++Network tool low-level, scanner cổ điển, phát triển exploit cho lỗi memory-corruptionNmap (phần core), payload native của Metasploit

Bash — chất keo cho automation security trên Linux

Bash là ngôn ngữ mặc định của shell. Bạn không thực sự “chọn” nó vì mỗi lần SSH vào một máy bạn đã đứng trong nó rồi. Với DevSecOps, các use case cốt lõi của Bash là: loop qua danh sách host, nối các CLI tool bằng pipe, chạy như cron job theo lịch, và các check nhanh khi xử lý incident.

Các construct đáng nhớ nằm lòng:

# Looping over a list of hosts read from a file
while read -r host; do
  echo "Scanning $host..."
  nmap -Pn -p 22,80,443 "$host" -oG - | grep -q "22/open" && echo "$host: SSH exposed"
done < hosts.txt

# Command substitution + conditionals
if ! systemctl is-active --quiet fail2ban; then
  echo "WARNING: fail2ban is not running" >&2
fi

# Safer script defaults
set -euo pipefail

set -euo pipefail là dòng quan trọng nhất cần đặt ở đầu bất kỳ Bash script nào liên quan đến security: -e khiến script exit ngay khi command đầu tiên fail, -u coi biến chưa được set là lỗi, và -o pipefail khiến cả pipeline fail nếu bất kỳ stage nào trong đó fail (chứ không chỉ stage cuối). Nếu thiếu dòng này, script sẽ âm thầm chạy tiếp sau khi gặp lỗi — đúng kiểu failure mode mà bạn không muốn có trong một security check.

Ví dụ: một security check nhỏ dựa trên log-grep. Script này scan auth log để tìm một chuỗi đăng nhập SSH thất bại dồn dập từ cùng một IP và flag bất kỳ trường hợp nào vượt ngưỡng — một brute-force detector tối giản, phù hợp để chạy trong cron job:

#!/usr/bin/env bash
set -euo pipefail

LOG_FILE="${1:-/var/log/auth.log}"
THRESHOLD=10

echo "Checking $LOG_FILE for SSH brute-force attempts (threshold: $THRESHOLD)..."

grep "Failed password" "$LOG_FILE" \
  | grep -oE "from [0-9]{1,3}(\.[0-9]{1,3}){3}" \
  | awk '{print $2}' \
  | sort \
  | uniq -c \
  | sort -rn \
  | while read -r count ip; do
      if (( count >= THRESHOLD )); then
        echo "ALERT: $ip had $count failed login attempts"
      fi
    done

Lưu thành check_ssh_bruteforce.sh, chmod +x rồi đưa vào cron:

# /etc/cron.d/ssh-bruteforce-check — run every 15 minutes
*/15 * * * * root /usr/local/bin/check_ssh_bruteforce.sh /var/log/auth.log >> /var/log/bruteforce-alerts.log 2>&1

Điểm yếu của Bash lộ ra rất nhanh khi logic phức tạp lên: không có cấu trúc dữ liệu thực sự ngoài array, thao tác string khá vất vả, và lỗi quoting rất dễ bị bỏ sót. Khi một “script nhanh” phình lên quá ~50 dòng, hoặc cần parse JSON/handle retry/gọi API, đó là tín hiệu để chuyển sang Python.

Python — ngôn ngữ mặc định của DevSecOps

Python là thứ gần nhất với một lingua franca trong DevSecOps. Nó đọc gần giống pseudocode, có standard library đồ sộ, và một hệ sinh thái package chuyên biệt cho security còn đồ sộ hơn: requests cho HTTP, paramiko/fabric cho automation qua SSH, boto3 cho AWS, python-nmap để wrap Nmap, scapy để craft packet, và các framework như bandit (SAST cho chính Python) cũng được viết bằng Python.

Ví dụ: một scanner đơn giản phát hiện secret bị lộ. Script này duyệt qua một cây thư mục và flag các file trông như chứa credential hardcode — một phiên bản rút gọn của những gì các tool như gitleaks hay trufflehog làm:

#!/usr/bin/env python3
"""secret_scan.py — flag likely hardcoded secrets in a source tree."""

import re
import sys
from pathlib import Path

PATTERNS = {
    "AWS Access Key": re.compile(r"AKIA[0-9A-Z]{16}"),
    "Generic API Key": re.compile(r"(?i)api[_-]?key\s*[:=]\s*['\"][A-Za-z0-9_\-]{16,}['\"]"),
    "Private Key Block": re.compile(r"-----BEGIN (RSA|EC|OPENSSH|DSA) PRIVATE KEY-----"),
    "Slack Token": re.compile(r"xox[baprs]-[0-9A-Za-z-]{10,}"),
}

SKIP_DIRS = {".git", "node_modules", "venv", "__pycache__"}


def scan_file(path: Path) -> list[tuple[str, str, int]]:
    findings = []
    try:
        text = path.read_text(errors="ignore")
    except (UnicodeDecodeError, PermissionError):
        return findings

    for line_no, line in enumerate(text.splitlines(), start=1):
        for name, pattern in PATTERNS.items():
            if pattern.search(line):
                findings.append((name, str(path), line_no))
    return findings


def main(root: str) -> int:
    root_path = Path(root)
    all_findings = []

    for path in root_path.rglob("*"):
        if any(part in SKIP_DIRS for part in path.parts):
            continue
        if path.is_file():
            all_findings.extend(scan_file(path))

    if all_findings:
        print(f"Found {len(all_findings)} potential secret(s):\n")
        for name, file_path, line_no in all_findings:
            print(f"  [{name}] {file_path}:{line_no}")
        return 1

    print("No secrets found.")
    return 0


if __name__ == "__main__":
    target = sys.argv[1] if len(sys.argv) > 1 else "."
    sys.exit(main(target))

Chạy bằng python3 secret_scan.py . và nó sẽ exit non-zero khi tìm thấy gì đó — đúng contract exit-code mà một CI pipeline cần để fail build khi phát hiện vấn đề.

Một pattern phổ biến thứ hai là gọi thẳng security API:

import requests

def check_vulners(cve_id: str, api_key: str) -> dict:
    resp = requests.get(
        "https://vulners.com/api/v3/search/id/",
        params={"id": cve_id},
        headers={"X-Api-Key": api_key},
        timeout=10,
    )
    resp.raise_for_status()
    return resp.json()

Điểm yếu chính của Python cho security tooling là distribution: ship một Python script nghĩa là ship (hoặc giả định có sẵn) một interpreter và các dependency — đây chính xác là thứ mà Go giải quyết.

Go — ngôn ngữ của security tooling hiện đại

Một phần lớn security tool được xây trong thập kỷ qua (nuclei, subfinder, trivy, gitleaks, amass, phần lớn toolkit của ProjectDiscovery, và các security tool cloud-native/Kubernetes nói chung) đều được viết bằng Go. Hai lý do chính:

  1. Single static binary. go build tạo ra một executable tự chứa duy nhất, không phụ thuộc runtime — copy nó sang bất kỳ máy Linux/macOS/Windows nào và nó chạy được ngay. Điều này cực kỳ quan trọng với các security tool cần chạy trong container tối giản, trên hệ thống air-gapped, hoặc như một phần của CI image mà không cần cài language runtime.
  2. Concurrency có sẵn. Goroutine và channel khiến việc “scan 10,000 host/URL đồng thời” trở thành một pattern tự nhiên và rẻ — đúng profile workload của scanner, crawler, và brute-forcer.

Ví dụ: một port checker nhỏ chạy concurrent, minh họa cả hai điểm trên:

package main

import (
	"fmt"
	"net"
	"sync"
	"time"
)

func checkPort(host string, port int, wg *sync.WaitGroup, results chan<- string) {
	defer wg.Done()
	address := fmt.Sprintf("%s:%d", host, port)
	conn, err := net.DialTimeout("tcp", address, 2*time.Second)
	if err == nil {
		conn.Close()
		results <- fmt.Sprintf("%s: OPEN", address)
	}
}

func main() {
	host := "127.0.0.1"
	ports := []int{22, 80, 443, 3306, 6379, 8080}

	var wg sync.WaitGroup
	results := make(chan string, len(ports))

	for _, port := range ports {
		wg.Add(1)
		go checkPort(host, port, &wg, results)
	}

	wg.Wait()
	close(results)

	for r := range results {
		fmt.Println(r)
	}
}

go build -o portcheck main.go tạo ra một binary duy nhất mà bạn có thể đưa cho đồng nghiệp hoặc bake vào một container image scratch/distroless — không cần pip install, không lo runtime version mismatch.

PowerShell — automation security cho Windows và Active Directory

Nếu Bash thống trị Linux thì PowerShell thống trị Windows — và cụ thể là Active Directory. Pipeline hướng object của nó (cmdlet truyền structured .NET object, không phải text) khiến nó phù hợp hơn Bash rất nhiều để query và thao tác những thứ như AD user, group membership, và event log. Đây cũng là ngôn ngữ được cả bên tấn công (offensive tooling như PowerView, PowerSploit, các script kiểu Mimikatz) và bên phòng thủ (script audit, hardening, detection) ưa chuộng, nên việc thành thạo cơ bản có giá trị cho cả góc nhìn red team lẫn blue team.

Ví dụ: tìm các AD account có password never expire (một finding phổ biến trong security audit):

Import-Module ActiveDirectory

Get-ADUser -Filter { PasswordNeverExpires -eq $true -and Enabled -eq $true } `
    -Properties PasswordNeverExpires, LastLogonDate |
    Select-Object Name, SamAccountName, LastLogonDate |
    Sort-Object LastLogonDate |
    Format-Table -AutoSize

Ví dụ: một security baseline check cục bộ, nhẹ nhàng:

$checks = @(
    @{ Name = "Windows Firewall (Domain)"; Test = { (Get-NetFirewallProfile -Profile Domain).Enabled } }
    @{ Name = "SMBv1 Disabled";            Test = { -not (Get-WindowsOptionalFeature -Online -FeatureName SMB1Protocol).State -eq "Enabled" } }
    @{ Name = "RDP NLA Enabled";           Test = { (Get-Item "HKLM:\System\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp").GetValue("UserAuthentication") -eq 1 } }
)

foreach ($check in $checks) {
    $result = & $check.Test
    $status = if ($result) { "PASS" } else { "FAIL" }
    Write-Host "[$status] $($check.Name)"
}

Vì PowerShell script cũng là một vector tấn công “living-off-the-land” ưa thích, các security team nên nắm rõ Set-ExecutionPolicy, script signing, và PowerShell logging/transcription (ScriptBlockLogging, Module Logging) — vừa để viết automation an toàn, vừa để phát hiện việc sử dụng độc hại.

Các ngôn ngữ khác, lướt qua

Ngôn ngữVị trí trong security tooling
RubyVề mặt lịch sử là ngôn ngữ của Metasploit; vẫn còn liên quan cho phát triển exploit và scripting nhanh khi làm việc với các module Metasploit. Ít phổ biến hơn cho tooling mới ngày nay.
RustĐang phát triển nhanh cho các security tool cần memory safety cộng với hiệu năng native — fuzzer, parser cho input không tin cậy (một lớp vulnerability phổ biến), và các CLI tool thế hệ mới (tốc độ kiểu ripgrep cộng với đảm bảo an toàn). Learning curve dốc hơn Go.
JavaScript / Node.jsThiết yếu cho web application security (khai thác phía browser, phân tích DOM-based XSS, headless browser automation qua Puppeteer/Playwright cho dynamic scanning) và để viết extension tùy chỉnh cho Burp Suite/ZAP hoặc các check CI trong pipeline dựa trên Node.
C / C++Là nền tảng của các tool cổ điển (phần core của Nmap, nhiều exploit PoC) và thiết yếu để hiểu các vulnerability memory-corruption (buffer overflow, use-after-free) ở mức mà exploit developer cần — nhưng hiếm khi là lựa chọn đầu tiên để viết automation mới ngày nay.

Editor cho công việc security và incident-response

Phần lớn việc edit hằng ngày diễn ra trong một IDE đầy đủ (VS Code, GoLand, PyCharm), nhưng công việc DevSecOps thường xuyên đặt bạn vào một máy remote chỉ có terminal — một phiên incident response, một bastion host bị khóa, một container không có GUI. Trong tình huống đó, biết đủ rõ một terminal editor để không “vật lộn” với nó là điều quan trọng:

EditorNhìn nhanh
vim / neovimModal, learning curve ban đầu dốc, có sẵn trên gần như mọi hệ Linux (hoặc dưới tên vi). Đáng để học ít nhất: i để insert, Esc để quay về normal mode, :wq để save và quit, /pattern để search, dd để xóa một dòng. Lựa chọn mặc định cho tình huống “tôi vừa SSH vào một máy trong lúc incident và cần edit config ngay bây giờ.”
nanoNon-modal, hiển thị các phím tắt ngay trên màn hình (^O để save, ^X để exit), gần như không có learning curve. Lựa chọn dự phòng tốt khi cần edit nhanh mà không muốn nghĩ về mode.
emacsExtensible, mặc định non-modal, mạnh mẽ nhưng nặng hơn và ít được cài sẵn phổ biến hơn vim/nano. Phổ biến hơn như một môi trường phát triển đầy đủ hơn là một editor edit nhanh trên máy remote.

Riêng với incident response: hạn chế tối đa những gì bạn cài thêm trên một máy có khả năng đã bị compromise. Nếu vim hoặc nano đã có sẵn (hầu như luôn luôn có), hãy dùng nó thay vì kéo thêm package mới có thể làm nhiễu trạng thái forensic hoặc đơn giản là không khả dụng trong môi trường air-gapped.

Version control — một tiền đề, không phải tùy chọn

Mọi script, playbook, và pipeline definition được nhắc trong note này đều thuộc về version control, vì những lý do tương tự như bất kỳ code nào khác: khả năng review, rollback, audit trail (ai đã thay đổi một security check và tại sao), và collaboration an toàn. Điều này càng đúng gấp đôi với security tooling, nơi một thay đổi không được review đối với một scan script hay một policy file có thể âm thầm vô hiệu hóa một control. Xem Version Control & Collaboration để biết các nền tảng Git, branching, và quy trình code review — không có gì trong note đó là security-specific, nhưng tất cả đều áp dụng trực tiếp vào cách bạn nên quản lý DevSecOps script và IaC.

Khái niệm chính

Idempotency trong security script

Một script scan host, apply firewall rule, hoặc rotate credential nên an toàn khi chạy hai lần. Script idempotent kiểm tra state hiện tại trước khi thay đổi nó (nếu rule chưa tồn tại thì thêm vào thay vì cứ thế thêm rule một cách mù quáng), điều này cực kỳ quan trọng khi cùng một script chạy cả interactive lẫn theo lịch (schedule).

Exit code như một contract với CI

Mọi security check dùng để gate một pipeline phải truyền đạt pass/fail thông qua exit code của nó (0 = pass, khác 0 = fail), chứ không chỉ qua text in ra. Đây là chi tiết duy nhất biến một script từ “thứ con người đọc” thành “thứ pipeline có thể enforce.”

python3 secret_scan.py . || { echo "Secrets found — failing build"; exit 1; }

Parse structured output của tool

Các security tool hiện đại ngày càng hỗ trợ output --json / -oJ chính xác là để script không phải regex-parse text dành cho con người đọc. Ưu tiên nmap -oX, trivy --format json, semgrep --json, v.v., và parse bằng thư viện JSON/XML native của ngôn ngữ thay vì scrape text đã format — output dạng format thay đổi giữa các version tool; JSON schema ổn định hơn nhiều.

import json, subprocess

result = subprocess.run(
    ["trivy", "image", "--format", "json", "myapp:latest"],
    capture_output=True, text=True, check=True,
)
report = json.loads(result.stdout)
critical = [
    v for r in report.get("Results", [])
    for v in r.get("Vulnerabilities", []) or []
    if v["Severity"] == "CRITICAL"
]
print(f"{len(critical)} CRITICAL vulnerabilities found")

Least privilege trong tooling, không chỉ trong infrastructure

Các script tự động hóa security task thường cần credential nâng quyền (một API token phạm vi rộng, root/sudo, một service account key). Áp dụng tư duy least-privilege cho credential của chính script cũng như bạn làm với một production service: scope token hẹp nhất có thể, tránh embed secret trực tiếp trong script (dùng environment variable hoặc secrets manager), và log những gì script đã làm, không phải secret nó đã dùng.

Chọn ngôn ngữ là một quyết định thiết kế, không phải sở thích cá nhân

Chọn ngôn ngữ cho một mảnh security tooling nên theo ràng buộc của nơi nó chạy và việc nó làm, chứ không chỉ theo ngôn ngữ bạn viết nhanh nhất: một check log một lần trên máy bạn đã SSH sẵn là việc của Bash; một scanner cần ship cho đồng nghiệp mà không cần cài Python là việc của Go; một cuộc audit AD là việc của PowerShell. Cố “chống lại” ngôn ngữ tự nhiên của môi trường thường tốn kém hơn thời gian tiết kiệm được khi dùng ngôn ngữ ưa thích.

Best Practices

Tài liệu tham khảo

Part of the DevSecOps Roadmap knowledge base.

Overview

DevSecOps is not a spectator role. Security engineers who can only read dashboards and file tickets are limited to the checks someone else already built. The moment you can write a script, you can automate a scan across a thousand hosts instead of clicking through one at a time, parse a tool’s ugly output into something a pipeline can gate on, glue together two tools that were never meant to talk to each other, or triage an incident by grepping logs faster than any GUI allows.

This note covers the languages that matter most in day-to-day DevSecOps work — what each is good for, a runnable example in each, and a comparison table to help you pick the right tool for a given job. The goal is not mastery of any single language; it’s having enough fluency in each to read other people’s tooling, write your own small utilities, and know which language to reach for. It builds on Introduction to DevSecOps, which covers the culture and process side of the discipline this note gives you the technical toolkit for.

Why this matters specifically for security work

Fundamentals

Language comparison at a glance

LanguageTypical security use caseExample tool written in it
BashGluing CLI tools together, looping over hosts, cron jobs, quick incident-response checksMost Linux hardening/audit scripts (e.g. lynis uses shell heavily)
PythonGeneral-purpose automation, API scripting, SAST/DAST helper tooling, exploit PoCsbandit (Python SAST), sqlmap, Scapy-based tools
GoConcurrent scanners, portable CLI security tools distributed as single binariesnuclei, trivy, gitleaks, subfinder
PowerShellWindows/Active Directory auditing, hardening, offensive AD toolingPowerView, PowerSploit, Microsoft’s own security baseline scripts
RubyExploit development and modules within an existing frameworkMetasploit Framework
RustMemory-safe fuzzers and parsers for untrusted input, high-performance CLI toolscargo-fuzz, RustScan
JavaScript/Node.jsWeb app security tooling, headless browser automation, CI checks in JS pipelinesPuppeteer/Playwright-based scanners, ESLint security plugins
C/C++Low-level network tools, classic scanners, exploit development for memory-corruption bugsNmap (core), Metasploit’s native payloads

Bash — the glue of Linux security automation

Bash is the default language of the shell. You don’t choose it so much as you’re already standing in it every time you SSH into a box. For DevSecOps, its core use cases are: looping over a list of hosts, chaining CLI tools together with pipes, running as scheduled cron jobs, and quick one-off checks during an incident.

Key constructs worth knowing cold:

# Looping over a list of hosts read from a file
while read -r host; do
  echo "Scanning $host..."
  nmap -Pn -p 22,80,443 "$host" -oG - | grep -q "22/open" && echo "$host: SSH exposed"
done < hosts.txt

# Command substitution + conditionals
if ! systemctl is-active --quiet fail2ban; then
  echo "WARNING: fail2ban is not running" >&2
fi

# Safer script defaults
set -euo pipefail

set -euo pipefail is the single most important line to put at the top of any security-relevant Bash script: -e exits on the first failing command, -u treats unset variables as errors, and -o pipefail makes a pipeline fail if any stage of it fails (not just the last one). Without it, scripts silently continue after errors — exactly the kind of failure mode you don’t want in a security check.

Example: a small log-grep security check. This script scans an auth log for a burst of failed SSH logins from a single IP and flags anything over a threshold — a minimal brute-force detector suitable for a cron job:

#!/usr/bin/env bash
set -euo pipefail

LOG_FILE="${1:-/var/log/auth.log}"
THRESHOLD=10

echo "Checking $LOG_FILE for SSH brute-force attempts (threshold: $THRESHOLD)..."

grep "Failed password" "$LOG_FILE" \
  | grep -oE "from [0-9]{1,3}(\.[0-9]{1,3}){3}" \
  | awk '{print $2}' \
  | sort \
  | uniq -c \
  | sort -rn \
  | while read -r count ip; do
      if (( count >= THRESHOLD )); then
        echo "ALERT: $ip had $count failed login attempts"
      fi
    done

Save it as check_ssh_bruteforce.sh, chmod +x it, and drop it in cron:

# /etc/cron.d/ssh-bruteforce-check — run every 15 minutes
*/15 * * * * root /usr/local/bin/check_ssh_bruteforce.sh /var/log/auth.log >> /var/log/bruteforce-alerts.log 2>&1

Bash’s weaknesses show up fast once logic gets complex: no real data structures beyond arrays, painful string manipulation, and easy-to-miss quoting bugs. When a “quick script” grows past ~50 lines or needs to parse JSON/handle retries/hit an API, that’s the signal to switch to Python.

Python — the default DevSecOps language

Python is the closest thing DevSecOps has to a lingua franca. It reads close to pseudocode, has a vast standard library, and an even vaster ecosystem of security-specific packages: requests for HTTP, paramiko/fabric for SSH automation, boto3 for AWS, python-nmap for wrapping Nmap, scapy for packet crafting, and frameworks like bandit (SAST for Python itself) being written in Python.

Example: a simple exposed-secrets scanner. This walks a directory tree and flags files that look like they contain hardcoded credentials — a stripped-down version of what tools like gitleaks or trufflehog do:

#!/usr/bin/env python3
"""secret_scan.py — flag likely hardcoded secrets in a source tree."""

import re
import sys
from pathlib import Path

PATTERNS = {
    "AWS Access Key": re.compile(r"AKIA[0-9A-Z]{16}"),
    "Generic API Key": re.compile(r"(?i)api[_-]?key\s*[:=]\s*['\"][A-Za-z0-9_\-]{16,}['\"]"),
    "Private Key Block": re.compile(r"-----BEGIN (RSA|EC|OPENSSH|DSA) PRIVATE KEY-----"),
    "Slack Token": re.compile(r"xox[baprs]-[0-9A-Za-z-]{10,}"),
}

SKIP_DIRS = {".git", "node_modules", "venv", "__pycache__"}


def scan_file(path: Path) -> list[tuple[str, str, int]]:
    findings = []
    try:
        text = path.read_text(errors="ignore")
    except (UnicodeDecodeError, PermissionError):
        return findings

    for line_no, line in enumerate(text.splitlines(), start=1):
        for name, pattern in PATTERNS.items():
            if pattern.search(line):
                findings.append((name, str(path), line_no))
    return findings


def main(root: str) -> int:
    root_path = Path(root)
    all_findings = []

    for path in root_path.rglob("*"):
        if any(part in SKIP_DIRS for part in path.parts):
            continue
        if path.is_file():
            all_findings.extend(scan_file(path))

    if all_findings:
        print(f"Found {len(all_findings)} potential secret(s):\n")
        for name, file_path, line_no in all_findings:
            print(f"  [{name}] {file_path}:{line_no}")
        return 1

    print("No secrets found.")
    return 0


if __name__ == "__main__":
    target = sys.argv[1] if len(sys.argv) > 1 else "."
    sys.exit(main(target))

Run it with python3 secret_scan.py . and it exits non-zero when it finds something — exactly the exit-code contract a CI pipeline needs to fail a build on detection.

A second common pattern is calling security APIs directly:

import requests

def check_vulners(cve_id: str, api_key: str) -> dict:
    resp = requests.get(
        "https://vulners.com/api/v3/search/id/",
        params={"id": cve_id},
        headers={"X-Api-Key": api_key},
        timeout=10,
    )
    resp.raise_for_status()
    return resp.json()

Python’s main weakness for security tooling is distribution: shipping a Python script means shipping (or assuming) an interpreter and dependencies, which is exactly what Go solves.

Go — the language of modern security tooling

A huge share of the security tools built in the last decade (nuclei, subfinder, trivy, gitleaks, amass, most of ProjectDiscovery’s toolkit, and Kubernetes/cloud-native security tools generally) are written in Go. Two reasons dominate:

  1. Single static binary. go build produces one self-contained executable with no runtime dependency — copy it to any Linux/macOS/Windows box and it runs. That matters enormously for security tools that need to run inside minimal containers, on air-gapped systems, or as part of a CI image without installing a language runtime.
  2. Built-in concurrency. Goroutines and channels make “scan 10,000 hosts/URLs concurrently” a natural, cheap pattern — exactly the workload profile of scanners, crawlers, and brute-forcers.

Example: a small concurrent port checker, illustrating both points:

package main

import (
	"fmt"
	"net"
	"sync"
	"time"
)

func checkPort(host string, port int, wg *sync.WaitGroup, results chan<- string) {
	defer wg.Done()
	address := fmt.Sprintf("%s:%d", host, port)
	conn, err := net.DialTimeout("tcp", address, 2*time.Second)
	if err == nil {
		conn.Close()
		results <- fmt.Sprintf("%s: OPEN", address)
	}
}

func main() {
	host := "127.0.0.1"
	ports := []int{22, 80, 443, 3306, 6379, 8080}

	var wg sync.WaitGroup
	results := make(chan string, len(ports))

	for _, port := range ports {
		wg.Add(1)
		go checkPort(host, port, &wg, results)
	}

	wg.Wait()
	close(results)

	for r := range results {
		fmt.Println(r)
	}
}

go build -o portcheck main.go produces a single binary you can hand to a colleague or bake into a scratch/distroless container image — no pip install, no runtime version mismatch.

PowerShell — Windows and Active Directory security automation

Where Bash rules Linux, PowerShell rules Windows — and specifically Active Directory. Its object-oriented pipeline (cmdlets pass structured .NET objects, not text) makes it far better suited than Bash to querying and manipulating things like AD users, group memberships, and event logs. It’s also the language of choice for both attackers (offensive tooling like PowerView, PowerSploit, Mimikatz-adjacent scripts) and defenders (auditing, hardening, and detection scripts), which makes basic fluency valuable for both red and blue perspectives.

Example: find AD accounts with passwords that never expire (a common finding in security audits):

Import-Module ActiveDirectory

Get-ADUser -Filter { PasswordNeverExpires -eq $true -and Enabled -eq $true } `
    -Properties PasswordNeverExpires, LastLogonDate |
    Select-Object Name, SamAccountName, LastLogonDate |
    Sort-Object LastLogonDate |
    Format-Table -AutoSize

Example: a lightweight local security baseline check:

$checks = @(
    @{ Name = "Windows Firewall (Domain)"; Test = { (Get-NetFirewallProfile -Profile Domain).Enabled } }
    @{ Name = "SMBv1 Disabled";            Test = { -not (Get-WindowsOptionalFeature -Online -FeatureName SMB1Protocol).State -eq "Enabled" } }
    @{ Name = "RDP NLA Enabled";           Test = { (Get-Item "HKLM:\System\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp").GetValue("UserAuthentication") -eq 1 } }
)

foreach ($check in $checks) {
    $result = & $check.Test
    $status = if ($result) { "PASS" } else { "FAIL" }
    Write-Host "[$status] $($check.Name)"
}

Because PowerShell scripts are also a favorite living-off-the-land attack vector, security teams should know Set-ExecutionPolicy, script signing, and PowerShell logging/transcription (ScriptBlockLogging, Module Logging) — both to write safe automation and to detect malicious use.

Other languages, briefly

LanguageWhere it fits in security tooling
RubyHistorically the language of Metasploit; still relevant for exploit development and quick scripting where Metasploit modules are involved. Less common for new tooling today.
RustGrowing fast for security tools needing memory safety plus native performance — fuzzers, parsers for untrusted input (a common vulnerability class), and newer-generation CLI tools (ripgrep-style speed with safety guarantees). Steeper learning curve than Go.
JavaScript / Node.jsEssential for web application security (browser-side exploitation, DOM-based XSS analysis, headless browser automation via Puppeteer/Playwright for dynamic scanning) and for writing custom Burp Suite/ZAP extensions or CI checks in a Node-based pipeline.
C / C++Underpins classic tools (Nmap’s core, many exploit PoCs) and is essential for understanding memory-corruption vulnerabilities (buffer overflows, use-after-free) at the level exploit developers need — but rarely the first choice for writing new automation today.

Editors for security and incident-response work

Most day-to-day editing happens in a full IDE (VS Code, GoLand, PyCharm), but DevSecOps work regularly puts you on a remote box with nothing but a terminal — an incident response session, a locked-down bastion host, a container with no GUI. In that situation, knowing a terminal editor well enough to not fight it matters:

EditorAt a glance
vim / neovimModal, steep initial learning curve, available on virtually every Linux system by default (or as vi). Worth learning at least: i to insert, Esc to go back to normal mode, :wq to save and quit, /pattern to search, dd to delete a line. The default choice for “I’m SSH’d into a box during an incident and need to edit a config right now.”
nanoNon-modal, shows its keyboard shortcuts on screen (^O to save, ^X to exit), essentially zero learning curve. Good fallback when you need a quick edit and don’t want to think about modes.
emacsExtensible, non-modal by default, powerful but heavier and less universally pre-installed than vim/nano. More common as a full development environment than a quick remote-box editor.

For incident response specifically: minimize what you install on a potentially compromised box. If vim or nano is already there (it almost always is), use it rather than pulling in new packages that could taint forensic state or simply aren’t available in an air-gapped environment.

Version control — a prerequisite, not optional

Every script, playbook, and pipeline definition covered in this note belongs in version control, for the same reasons as any other code: reviewability, rollback, audit trail (who changed a security check and why), and safe collaboration. This is doubly true for security tooling, where an unreviewed change to a scan script or a policy file can silently disable a control. See Version Control & Collaboration for Git fundamentals, branching, and code review workflow — nothing in that note is security-specific, but all of it applies directly to how you should manage DevSecOps scripts and IaC.

Key Concepts

Idempotency in security scripts

A script that scans hosts, applies a firewall rule, or rotates a credential should be safe to run twice. Idempotent scripts check current state before changing it (if rule not present, add it rather than blindly add rule), which matters enormously when the same script runs both interactively and on a schedule.

Exit codes as the CI contract

Every security check meant to gate a pipeline must communicate pass/fail through its exit code (0 = pass, non-zero = fail), not through printed text alone. This is the single detail that turns a script from “something a human reads” into “something a pipeline can enforce.”

python3 secret_scan.py . || { echo "Secrets found — failing build"; exit 1; }

Parsing structured tool output

Modern security tools increasingly support --json / -oJ output specifically so scripts don’t have to regex-parse human-readable text. Prefer nmap -oX, trivy --format json, semgrep --json, etc., and parse with the language’s native JSON/XML library rather than scraping formatted text — formatted output changes between tool versions; JSON schemas are far more stable.

import json, subprocess

result = subprocess.run(
    ["trivy", "image", "--format", "json", "myapp:latest"],
    capture_output=True, text=True, check=True,
)
report = json.loads(result.stdout)
critical = [
    v for r in report.get("Results", [])
    for v in r.get("Vulnerabilities", []) or []
    if v["Severity"] == "CRITICAL"
]
print(f"{len(critical)} CRITICAL vulnerabilities found")

Least privilege in tooling, not just infrastructure

Scripts that automate security tasks often need elevated credentials (an API token with wide scope, root/sudo, a service account key). Apply the same least-privilege thinking to the script’s own credentials as you would to a production service: scope tokens narrowly, avoid embedding secrets in the script itself (use environment variables or a secrets manager), and log what the script did, not the secret it used.

Language choice as a design decision, not a preference

Picking a language for a piece of security tooling should follow the constraints of where it runs and what it does, not just what you’re personally fastest in: a one-off log check on a box you’re already SSH’d into is a Bash job; a scanner that needs to ship to teammates without a Python install is a Go job; an AD audit is a PowerShell job. Fighting the environment’s native language usually costs more than the time saved by using a favorite one.

Best Practices

References