Three years ago a Razorpay engineer would have said: "We scan our running containers. We rotate our secrets. We are secure." Then SolarWinds happened. Then XZ Utils. Then log4shell. In all three cases, the attacker did not break into the running application. They broke into something earlier - the build system, the open source dependency, the update mechanism. The application was compromised before a single line of your code ran. This is the shift that defines advanced DevSecOps. The perimeter is no longer the network edge or the container boundary. It is the entire chain from developer laptop to production deployment. Every link in that chain is an attack surface: your developer's git commit, your CI runner, your base image, your third-party library, your artifact registry. If any link is compromised and you cannot detect it, your production system is compromised too. This module covers the eight topics that close those gaps. Each one maps to a real class of attack that has hit production systems at companies like Zerodha, Swiggy, and Hotstar. By the end, you will have working commands for every control and understand exactly what each one prevents. Developer commit | v Build pipeline (CI runner) <- XZ Utils attack vector | v Third-party dependencies <- log4shell attack vector | v Artifact registry | v Deployment to Kubernetes <- supply chain attack lands here | v Running production system ---
### What supply chain attacks are and why they are so dangerous A **supply chain attack** does not target your application directly. It targets something your application trusts - a build tool, a dependency, a CI plugin, an update server. Once that trusted component is compromised, the attacker gets a free ride into every system that uses it. The SolarWinds attack in 2020 compromised the build server of a network monitoring company. A malicious update was signed with the company's legitimate certificate and pushed to 18,000 customers including government agencies. Nobody's perimeter defenses fired because the software looked completely legitimate. The XZ Utils attack in 2024 targeted a compression library used by SSH on most Linux systems. An attacker spent two years building trust in the open source project before inserting a backdoor. It was caught only by accident. The industry's answer to this class of attack has two parts: **SLSA** (Supply-chain Levels for Software Artifacts) proves how trustworthy your build process is, and **Sigstore** cryptographically proves that a specific artifact came from a specific build and has not been modified since. ### Understanding SLSA levels and what each one proves **SLSA** (pronounced "salsa") is a framework from Google that defines four levels of supply chain integrity. Think of it like a certification for your build process - higher levels mean an attacker needs to compromise more things simultaneously to tamper with your artifact. | Level | What it proves | |:------|:--------------| | SLSA 1 | Build process is documented and produces a provenance record | | SLSA 2 | Build is version-controlled and the provenance is signed by the build service | | SLSA 3 | Build runs in an isolated, hardened environment - no human can modify the build mid-run | | SLSA 4 | Two-person review of all build changes, hermetic reproducible builds | Most teams should target SLSA 2 or 3. SLSA 4 is for critical infrastructure like payment processors. The provenance record answers three questions: What source code went in? Which build system ran it? What came out? Without provenance, you cannot answer any of these after an incident. ### Signing container images with Cosign **Cosign** is the signing tool from the Sigstore project. It attaches a cryptographic signature to a container image so anyone can verify it was built by your CI pipeline and has not been modified since. Think of it like a wax seal on a letter - if the seal is intact, the letter has not been opened. ```bash ## Install cosign curl -O https://github.com/sigstore/cosign/releases/latest/download/cosign-linux-amd64 chmod +x cosign-linux-amd64 sudo mv cosign-linux-amd64 /usr/local/bin/cosign ## Keyless signing using OIDC identity from GitHub Actions ## This runs inside your CI pipeline -- no long-lived keys to manage cosign sign --yes registry.razorpay.internal/payment-service:v2.4.1 ## Verify the signature before deploying cosign verify \ --certificate-identity="https://github.com/razorpay/payment-service/.github/workflows/build.yml@refs/heads/main" \ --certificate-oidc-issuer="https://token.actions.githubusercontent.com" \ registry.razorpay.internal/payment-service:v2.4.1 ``` > **Note:** Keyless signing means Cosign uses your CI pipeline's OIDC token (a short-lived identity token from GitHub Actions) instead of a long-lived signing key. The signature is tied to the specific workflow file that ran it. An attacker cannot forge this without compromising your GitHub Actions environment. The **Rekor transparency log** is a public, append-only ledger that records every signature. When you sign an image, the signature is written to Rekor. When you verify, Rekor is checked. This means even if an attacker signed a malicious image with a stolen key, the signature timestamp would be visible in the public log. ### Generating and scanning SBOMs with Syft and Grype A **Software Bill of Materials (SBOM)** is a complete list of every library, package, and component inside your container image - like an ingredients list for software. Without an SBOM, when a new CVE drops you have to manually check every service to see if it uses the affected library. With an SBOM, you query it in seconds. ```bash ## Install Syft curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin ## Generate SBOM for a container image in CycloneDX format ## CycloneDX is the industry standard format supported by most vulnerability scanners syft registry.razorpay.internal/payment-service:v2.4.1 -o cyclonedx-json > payment-service-sbom.json ## Install Grype curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b /usr/local/bin ## Scan the SBOM for CVEs -- fail if any CRITICAL vulnerabilities found grype sbom:payment-service-sbom.json --fail-on critical ## Scan a running image directly (Grype generates the SBOM internally) grype registry.razorpay.internal/payment-service:v2.4.1 -o table ``` > **Note:** `--fail-on critical` exits with a non-zero code if any CRITICAL CVE is found. Put this in your CI pipeline promotion gate - an image with a CRITICAL CVE never reaches the production registry. When log4shell dropped in December 2021, teams without SBOMs spent days manually auditing every service. Teams with SBOMs ran one grep command: `cat *.json | grep log4j` and had a complete impact list in minutes. > 💡 **Tip:** Attach the SBOM to the container image as an OCI attestation using `cosign attest`. This keeps the SBOM co-located with the image in the registry and makes it impossible to lose or separate from the artifact it describes. > 🔴 **Common Mistake:** Generating the SBOM from the Dockerfile rather than the final built image. The Dockerfile lists what you intended to install. The built image contains what actually got installed, including transitive dependencies pulled in by your package manager. Always generate the SBOM from the final image. ---
### Why secrets end up in code and why it keeps happening Secrets end up in code for one reason: it is the path of least resistance. A developer needs a database password to test locally. They put it in a `.env` file. Three months later they commit the `.env` file by mistake. Or they hardcode an API key in a script "just for testing" and forget to remove it before pushing. Or a legacy service has a secret baked into its config from 2019 that nobody noticed. The dangerous part is that git history is permanent. Even if you delete the secret from the latest commit, it still exists in every commit before the deletion. Anyone who clones the repository can run `git log -p` and find it. GitHub's own research found that millions of secrets are committed to public repositories every year, and automated bots scan for them within seconds of a push. The fix has three layers: stop secrets before they commit (pre-commit hooks), scan every push on the server (CI scanning), and audit historical commits for secrets that slipped through (retrospective scanning). ### Setting up Gitleaks as a pre-commit hook **Gitleaks** is a fast secrets scanner that checks your staged files before they are committed. It uses both pattern matching (looking for strings that look like AWS keys, GitHub tokens, private keys) and entropy analysis (looking for high-randomness strings that are likely secrets even if they do not match a known pattern). ```bash ## Install Gitleaks wget https://github.com/gitleaks/gitleaks/releases/latest/download/gitleaks_linux_x64.tar.gz tar -xzf gitleaks_linux_x64.tar.gz sudo mv gitleaks /usr/local/bin/ ## Install pre-commit framework (manages hooks across languages) pip install pre-commit ## Create .pre-commit-config.yaml in your repository root cat > .pre-commit-config.yaml << 'EOF' repos: - repo: https://github.com/gitleaks/gitleaks rev: v8.18.0 hooks: - id: gitleaks EOF ## Install the hook into .git/hooks/pre-commit pre-commit install ## Test it -- create a file with a fake AWS key and try to commit echo 'AWS_SECRET_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY' > test-secret.txt git add test-secret.txt git commit -m "test" ## Gitleaks will block the commit and print: "leaks found: 1" ``` > **Note:** Gitleaks uses a `.gitleaks.toml` config file where you can add allowlists for known false positives - for example, test fixtures that look like secrets but are intentionally fake. Without an allowlist, developers will start ignoring the hook because of noise, which defeats the purpose entirely. ### Scanning git history for old secrets with TruffleHog **TruffleHog** specialises in deep git history scanning. Unlike Gitleaks which scans staged files, TruffleHog walks every commit in the repository history and checks for secrets that were committed and later deleted. A secret deleted from the codebase six months ago may still be valid and actively exploitable. ```bash ## Install TruffleHog curl -sSfL https://raw.githubusercontent.com/trufflesecurity/trufflehog/main/scripts/install.sh | sh -s -- -b /usr/local/bin ## Scan entire git history of a repository ## --only-verified checks if found secrets are actually valid (makes API calls to verify) trufflehog git file://. --only-verified ## Scan a specific GitHub repository trufflehog github --repo=https://github.com/swiggy-internal/order-service ## Scan only commits since a specific date (faster for large repos) trufflehog git file://. --since-commit=HEAD~100 ``` > ⚠️ **Security:** When TruffleHog finds a secret with `--only-verified`, it means the secret is still active and callable. Revoke it immediately before doing anything else - before committing a fix, before investigating how it got there, before telling anyone. A live secret in a public repo can be stolen within seconds of discovery. ### What to do when a secret is found The instinct when you find a secret in git history is to delete the commit or rewrite history with `git filter-branch`. This is wrong. Deleting the commit does not revoke the secret. The secret may already have been cloned, cached, or exploited. The correct sequence is: 1. Revoke the secret immediately with the service provider (rotate the AWS key, invalidate the GitHub token) 2. Check access logs for the service the secret belongs to - look for unexpected API calls 3. Replace the secret with a new one injected from Vault or Secrets Manager 4. Clean git history with `git filter-repo` (the modern replacement for filter-branch) 5. Force-push the cleaned history and require all team members to re-clone > 📌 **Remember:** Cleaning git history does not protect you if the repository was ever public or if it was cloned before the cleanup. Treat every discovered secret as already compromised. Revocation is the only real fix. ---
### What fuzzing is and why scanners miss what fuzzing finds **Fuzzing** is the practice of throwing massive amounts of unexpected, malformed, and random input at a program to find crashes, hangs, and unexpected behaviour. It finds bugs that no human would think to test and that static analysis tools cannot detect - because they require the code to actually run with bad input to manifest. Think of a lock picker versus a security auditor reading the lock's design specs. The auditor reads the blueprints and says "this looks secure." The lock picker tries ten thousand different key combinations until one accidentally works. Fuzzing is the lock picker. SAST tools like SonarQube find issues by reading code without running it. They are excellent for SQL injection patterns and hardcoded secrets. They cannot find: buffer overflows that only happen with a 65537-byte input, JSON parsers that crash on a specific Unicode sequence, or authentication bypasses that only trigger when two specific fields are set simultaneously. Fuzzing finds all of these. ### AFL++ for binary and native code fuzzing **AFL++** (American Fuzzy Lop plus plus) is the most widely used fuzzer for C and C++ programs. It instruments the target binary so it can measure which code paths each input reaches, then mutates inputs to maximise coverage. When it finds an input that causes a crash, it saves it as a test case. ```bash ## Install AFL++ sudo apt-get install -y afl++ ## Compile your target with AFL++ instrumentation ## afl-clang-fast is a wrapper around clang that adds coverage tracking afl-clang-fast -o target_fuzz target.c ## Create a seed corpus -- small valid inputs AFL++ will mutate mkdir corpus echo "valid_input" > corpus/seed1 echo '{"user": "rahul"}' > corpus/seed2 ## Run the fuzzer -- -i is input corpus, -o is output directory for crashes afl-fuzz -i corpus -o findings -- ./target_fuzz @@ ## @@ is replaced by AFL++ with the path to each mutated input file ``` > **Note:** AFL++ uses coverage-guided mutation - it tracks which branches in the code each input reaches, then deliberately mutates inputs to reach new branches. This is far more efficient than pure random fuzzing because it actively explores unexplored code paths. ### API fuzzing with RESTler Most DevSecOps teams work with APIs not binary programs. **RESTler** from Microsoft is a stateful REST API fuzzer that reads your OpenAPI specification and automatically generates sequences of API calls with unexpected values to find authentication bypasses, injection flaws, and logic errors. ```bash ## Install RESTler (requires Docker) docker pull mcr.microsoft.com/restler/restler:latest ## Run RESTler against a running API ## --grammar_file is generated from your OpenAPI spec docker run --rm \ -v $(pwd)/api-spec:/spec \ mcr.microsoft.com/restler/restler:latest \ fuzz \ --target_ip 10.0.1.50 \ ## your API server IP --target_port 8080 \ --openapi_spec /spec/openapi.json \ --time_budget 1 ## run for 1 hour ``` > 💡 **Tip:** Run RESTler in your staging environment, not production. It will make thousands of API calls including attempts to create, modify, and delete resources. Run it after every major API change, not on every PR - the run time is too long for PR gates. > 🔴 **Common Mistake:** Fuzzing only happy-path inputs. The most dangerous bugs are found at boundaries - maximum field lengths, null bytes, extremely large numbers, Unicode edge cases, and empty strings. Seed your corpus with these boundary values explicitly rather than relying on AFL++ to discover them through mutation. ### Fuzzing a Python JSON parser with Atheris Most Swiggy and Razorpay services process JSON from external sources - order payloads, webhook bodies, configuration files. A JSON parser that crashes on unexpected input is a denial-of-service vulnerability. **Atheris** is Google's Python fuzzing library that works like AFL++ but targets Python code directly, without needing to compile anything. ```python ## json_fuzzer.py ## Fuzz a JSON parsing function to find inputs that cause crashes or hangs import atheris import sys import json def parse_order_payload(data): """ Simulate the order service JSON parser. In production this would be your actual request parsing logic. """ try: parsed = json.loads(data) ## Simulate the kind of field access your application does if isinstance(parsed, dict): order_id = parsed.get("order_id", "") amount = float(parsed.get("amount", 0)) ## crashes if amount is non-numeric items = parsed.get("items", []) item_count= len(items) ## crashes if items is not iterable ## Simulate nested access that could fail on unexpected structure if items: first_item = items[0] if isinstance(first_item, dict): price = float(first_item.get("price", 0)) except (ValueError, TypeError, OverflowError): pass ## expected exceptions -- not a bug ## Any other unhandled exception is a potential bug Atheris will report def fuzz_one_input(data): """ Entry point Atheris calls for each generated input. data is raw bytes -- decode before passing to the parser. """ try: text = data.decode("utf-8", errors="ignore") parse_order_payload(text) except Exception as e: ## Unexpected exceptions crash Atheris and save the input as a finding raise if __name__ == "__main__": ## Seed corpus -- valid JSON inputs Atheris will mutate ## More varied seeds = better coverage faster atheris.Setup(sys.argv, fuzz_one_input) atheris.Fuzz() ``` ```bash ## Install Atheris pip install atheris ## Run the fuzzer -- it will print findings and save crash inputs to ./crash-* python json_fuzzer.py -runs=100000 ## run 100,000 iterations ## If a crash is found, reproduce it with the saved input python json_fuzzer.py crash-abc123 ## replay the specific crash input ``` > **Note:** Atheris instruments your Python code using libFuzzer under the hood, tracking which code branches each input reaches and mutating inputs to hit new branches. The `except (ValueError, TypeError, OverflowError)` block above is intentional - these are expected from malformed JSON. You only want to catch truly unexpected exceptions like `AttributeError` or `RecursionError` that indicate a logic bug in your parser. ---
### Why per-feature threat modeling breaks down at 50 engineers STRIDE threat modeling works well when one team owns one service and a senior engineer can review every new feature. At Razorpay scale - 200 engineers across 80 microservices, with dozens of features shipping every week - manual threat modeling creates a bottleneck. Either security reviews every PR and becomes the blocker, or threat modeling gets skipped entirely. The answer is to embed threat modeling into the engineering process as a structured, repeatable activity rather than an ad-hoc security review. This means templates, tooling, and clear triggers for when a threat model is required. ### STRIDE at the system level for microservices You have already used STRIDE at the application level in the Foundations module. At the microservice level, STRIDE applies to data flows between services, not just user inputs. Every arrow in your service mesh diagram is a threat surface. The six STRIDE threats mapped to microservice scenarios: | Threat | Microservice example | |:-------|:--------------------| | Spoofing | Service A claims to be Service B when calling the payment API | | Tampering | Message in Kafka queue modified between producer and consumer | | Repudiation | No audit log of which service triggered a refund | | Info Disclosure | Internal service error messages leaked to external API response | | Denial of Service | One service floods another with retries during an outage | | Elevation of Privilege | Order service calls admin endpoint it should not have access to | The tool that makes this scalable is a **Data Flow Diagram (DFD)**. Draw every service, every data store, and every external actor. Draw every arrow showing data movement. Every arrow that crosses a trust boundary needs a threat model entry. External User | | HTTPS (trust boundary: internet -> DMZ) v API Gateway -----> Auth Service | | | (trust boundary: | JWT validation | DMZ -> internal) | v v Order Service ------> Payment Service | | v v orders_db payments_db (trust boundary: (trust boundary: service -> data) service -> data) Every arrow crossing a boundary gets STRIDE applied. This makes threat modeling systematic rather than dependent on one person's memory. ### Automating threat model generation with the Microsoft Threat Modeling Tool The **Microsoft Threat Modeling Tool** is a free desktop application that lets you draw DFDs and automatically generates STRIDE threats for every data flow. For each threat it generates, you record a mitigation and mark it as addressed or accepted. **pytm** is a Python library that defines architecture as code and generates both the DFD diagram and the full STRIDE threat list automatically. The reason it beats a spreadsheet for engineering teams is simple: it lives in the repository alongside the service code, gets reviewed in pull requests, and stays in sync with architecture changes. A spreadsheet gets updated once and then drifts. For teams already doing everything as code -- infrastructure, pipelines, policies -- pytm fits naturally into that workflow. ```bash ## For teams preferring CLI-based threat modeling, pytm is a Python library pip install pytm ## Define your architecture as code -- pytm generates the DFD and threats cat > threat_model.py << 'EOF' from pytm import TM, Server, Datastore, Dataflow, Boundary, Actor tm = TM("Swiggy Order Service") internet = Boundary("Internet") internal = Boundary("Internal Network") user = Actor("Customer", inBoundary=internet) api_gw = Server("API Gateway", inBoundary=internet) order_svc= Server("Order Service", inBoundary=internal) orders_db= Datastore("Orders DB", inBoundary=internal) ## Define data flows -- each flow gets STRIDE threats automatically generated Dataflow(user, api_gw, "Place Order", protocol="HTTPS") Dataflow(api_gw, order_svc, "Forward Order", protocol="HTTP") Dataflow(order_svc, orders_db, "Write Order", protocol="SQL") tm.process() EOF python threat_model.py --report report.html ``` > **Note:** `pytm` generates an HTML threat report listing every STRIDE threat for every data flow you defined. It also generates a visual DFD diagram. The output is a living document - check it into your repository alongside the service code so threat models evolve with the architecture. > 📌 **Remember:** A threat model is only useful if it includes mitigations. For every threat listed, record: is this mitigated? How? Or is it accepted as a known risk? An unreviewed threat model is worse than no threat model - it gives false confidence. ---
### What security chaos engineering is and why you need it Your Falco rules are deployed. Your network policies are in place. Your IR runbook is written. But have you ever actually verified that Falco fires when someone executes a shell inside a container? Have you confirmed your on-call engineer can follow the runbook under pressure at 2 AM? Have you checked whether your network policy actually blocks lateral movement or just claims to? **Security Chaos Engineering** answers these questions by deliberately attacking your own systems in a controlled way to measure whether your defenses actually work. The core principle is simple: defenses that have never been tested under realistic conditions should be treated as untested. Detection rules that have never fired may be broken. IR runbooks that have never been executed have gaps. Netflix pioneered this approach with their Game Days - scheduled sessions where engineers deliberately break production systems to find gaps before attackers do. Security Chaos Engineering applies the same discipline to security controls. ### Testing detection coverage with Atomic Red Team **Atomic Red Team** is a library of small, contained attack simulations mapped to the MITRE ATT&CK framework. Each "atomic test" simulates one specific attacker technique - running a shell in a container, dumping credentials from memory, making unexpected outbound connections - in a controlled, reversible way. ```bash ## Install Atomic Red Team (PowerShell on Linux via pwsh) sudo apt-get install -y powershell pwsh -Command "Install-Module -Name invoke-atomicredteam -Force" ## List all available tests for the T1059.004 technique ## T1059.004 = Unix Shell execution (common post-exploitation step) pwsh -Command "Invoke-AtomicTest T1059.004 -ShowDetailsBrief" ## Run the test and see if Falco detects it ## First, watch Falco logs in another terminal: sudo journalctl -fu falco pwsh -Command "Invoke-AtomicTest T1059.004 -TestNumbers 1" ``` > **Note:** T1059.004 is the MITRE ATT&CK technique ID for "Unix Shell." Every Atomic Red Team test is mapped to a technique ID. This makes it easy to cross-reference: if Falco has a rule for T1059.004 and the test does not trigger it, the rule is broken and needs fixing. If installing PowerShell on Linux is not practical for your environment, the same detection tests can be simulated directly with bash. The goal is identical - trigger the kernel event that Falco monitors, then verify the alert fires. ```bash ## Bash-native simulation -- no PowerShell required ## These commands simulate the most common attacker techniques ## Simulate T1059.004: shell execution inside a running container ## Watch for Falco alert: "Terminal shell in container" kubectl exec -n production deploy/order-service -- /bin/bash -c "id && whoami" ## Simulate T1046: network service scanning (unexpected outbound connection) ## Watch for Falco alert: "Unexpected outbound connection" kubectl exec -n production deploy/order-service -- \ curl -s --max-time 3 http://169.254.169.254/latest/meta-data/ || true ## 169.254.169.254 is the AWS metadata endpoint -- a common attacker target ## Simulate T1070.003: clearing shell history (covering tracks) ## Watch for Falco alert: "Shell history tampering" kubectl exec -n production deploy/order-service -- \ /bin/bash -c "history -c && unset HISTFILE" ## Check Falco caught all three within 30 seconds sleep 5 kubectl logs -n falco daemonset/falco --since=1m | grep -E "shell|outbound|history" ``` > 📌 **Remember:** Run these bash simulations with the on-call engineer watching the Falco dashboard in real time. The point is not just to confirm the alert fires - it is to confirm the engineer sees it, understands it, and knows which runbook to follow. The human response is as important as the technical detection. A security chaos test follows four steps: define a hypothesis, run the attack simulation, measure the detection, and record the result. ```python ## detection_validator.py ## Runs an attack simulation and checks whether the expected alert fired import subprocess import time import requests def run_detection_test(test_name, attack_command, alert_keyword, wait_seconds=10): """ Run an attack simulation and verify the expected alert appeared in the SIEM. Returns True if the detection fired, False if it was missed. Args: test_name : Human-readable name of the test attack_command: Shell command that simulates the attack alert_keyword : String to search for in alert logs wait_seconds : How long to wait for the alert to appear """ print(f"\n[TEST] {test_name}") print(f" Running: {attack_command}") ## Execute the attack simulation subprocess.run(attack_command, shell=True, capture_output=True) ## Wait for the detection pipeline to process the event time.sleep(wait_seconds) ## Query your SIEM API for the expected alert ## Replace with your actual SIEM endpoint (Elasticsearch, Splunk, etc.) response = requests.get( "http://elasticsearch.internal:9200/falco-alerts/_search", json={"query": {"match": {"rule": alert_keyword}}}, timeout=10 ) hits = response.json()["hits"]["total"]["value"] if hits > 0: print(f" PASS -- alert fired ({hits} events)") return True else: print(f" FAIL -- no alert found for keyword: {alert_keyword}") return False ## Run a detection validation suite tests = [ { "name" : "Shell spawned inside container", "command" : "kubectl exec deployment/order-service -- /bin/bash -c 'id'", "keyword" : "Terminal shell in container" }, { "name" : "Unexpected outbound connection", "command" : "kubectl exec deployment/order-service -- curl http://10.0.99.1:4444", "keyword" : "Unexpected outbound connection" }, ] results = [run_detection_test(**t) for t in tests] passed = sum(results) print(f"\nResults: {passed}/{len(results)} detections validated") if passed < len(results): print("ACTION REQUIRED: Fix broken detection rules before next release") exit(1) ``` > 💡 **Tip:** Run detection validation tests on a schedule - weekly at minimum. Falco rules can break silently after a kernel upgrade or a change to the container runtime. You want to discover a broken detection rule in a scheduled test, not during a real incident. > 🔴 **Common Mistake:** Running chaos tests in production without notifying the on-call team. The point of security chaos engineering is to measure defenses, not to create real incidents. Always run tests in staging first, and when running in production, coordinate with the on-call engineer so they know the alerts they are seeing are simulated. ---
Three years ago a Razorpay engineer would have said: "We scan our running containers. We rotate our secrets. We are secu...
What supply chain attacks are and why they are so dangerous A supply chain attack does not target your application direc...
Why secrets end up in code and why it keeps happening Secrets end up in code for one reason: it is the path of least res...
What fuzzing is and why scanners miss what fuzzing finds Fuzzing is the practice of throwing massive amounts of unexpect...
Why per-feature threat modeling breaks down at 50 engineers STRIDE threat modeling works well when one team owns one ser...
What security chaos engineering is and why you need it Your Falco rules are deployed. Your network policies are in place...
Why standard Kubernetes NetworkPolicy is not enough The standard Kubernetes NetworkPolicy resource operates at Layer 3 a...
The difference between a zero-day and a known CVE A zero-day is a vulnerability that has no patch available yet - the ve...
What CIS Benchmarks are and why they matter for audits The Center for Internet Security (CIS) publishes detailed configu...
What you are building A complete security gate for a containerised service covering the full supply chain: SBOM generati...
Tool reference Tool Purpose Key command cosign sign Sign container image cosign sign --yes image:tag cosign verify Verif...
Aligns directly with DevOps, Site Reliability (SRE), and Platform Engineering job descriptions.