L1
·
Quiz
·
Lab
L2
·
Quiz
·
Lab
L3
·
Quiz
·
Lab
L4
·
Quiz
·
Lab
Module Test
Module 3 · Lesson 1

What Linters and Static Analyzers Do

Catching defects before the code ever runs — and why that matters at scale.
How do automated tools find bugs that human reviewers miss, and what are the limits of that automation?

The Mars Climate Orbiter was lost in 1999 to a unit-conversion error that code review failed to catch — a bug a modern type-checked static analyzer would have flagged as an error on the first compile. That failure cost $327 million. NASA's Jet Propulsion Laboratory subsequently mandated the use of formal static analysis on all flight software, embedding it directly into build pipelines rather than treating it as an optional quality gate.

Linting vs. Static Analysis: The Distinction

Linting is a lightweight form of static analysis focused on style, syntax, and common anti-patterns. The term comes from lint, a 1978 Unix tool written by Stephen Johnson at Bell Labs to flag suspicious C constructs. Modern linters such as ESLint, Pylint, and RuboCop operate on the abstract syntax tree (AST) of a file without executing it, applying rule sets that encode known bad patterns.

Static analysis is the broader discipline: it includes data-flow analysis, taint tracking, inter-procedural analysis, and formal verification. Tools like SonarQube, Coverity, and Facebook's Infer go further than linters — they reason about how values move through the program across function boundaries, catching null-pointer dereferences, SQL injection paths, and resource leaks that simple pattern matching misses.

The practical difference in a team workflow is coverage vs. speed. A linter runs in under a second on a file; a deep static analyzer may take minutes on a large codebase. Both have a place in the pipeline, at different stages.

AST analysis Parsing source code into a tree structure representing its syntax, then inspecting that tree for rule violations — no code execution required.
Data-flow analysis Tracking how values are assigned and propagated through a program to detect conditions like uninitialized variables or tainted input reaching sensitive sinks.
False positive A warning issued for code that is actually correct. High false-positive rates erode developer trust and cause teams to suppress or ignore tooling.
A Real Precedent: Heartbleed and What Static Analysis Would Have Caught

The Heartbleed vulnerability (CVE-2014-0160), disclosed April 2014, was a buffer over-read in OpenSSL's heartbeat extension. The bug was a missing bounds check on a user-supplied length value before memcpy. Analysis conducted after the disclosure by researchers at Carnegie Mellon's CyLab Security and Privacy Institute showed that both Coverity Scan and Klocwork — tools available and free for open source projects at the time — would have flagged the specific memcpy call as an unvalidated size issue.

The OpenSSL project was not running those tools in its CI pipeline in 2012 when the vulnerable code was introduced. After Heartbleed, the OpenSSL Foundation implemented mandatory Coverity scanning on every commit. This is a documented, publicly available lesson in the cost of treating static analysis as optional.

Key Point

Static analysis tools do not replace code review. They eliminate the category of defects that tools are better at than humans — consistent pattern matching at scale — so human reviewers can focus on logic, architecture, and intent.

Categories of Issues Each Tool Type Finds

Understanding what each tool class targets helps teams select the right tool for the right pipeline stage.

Tool Class Primary Targets Example Tools Typical Trigger
Linter Style, syntax, naming, simple anti-patterns ESLint, Pylint, RuboCop, golangci-lint Pre-commit / on save
Type Checker Type mismatches, null safety, interface violations mypy, TypeScript compiler, Sorbet, Flow Pre-commit / CI
SAST (Security) Injection flaws, unsafe deserialization, hardcoded secrets Semgrep, Bandit, Brakeman, CodeQL PR check / nightly
Deep SA Null deref, race conditions, memory leaks, inter-proc bugs Coverity, Infer, Klocwork, Polyspace Nightly / pre-release
Workflow Principle

Linters and type checkers should block the commit. Deep static analysis should block the release. SAST tools should block the merge — but require triage to avoid paralysis from false positives in legacy codebases.

The False-Positive Problem

Every team that deploys static analysis confronts the same challenge: tools that cry wolf too often get silenced. The 2016 Facebook Infer deployment at Facebook engineering is a well-documented case. The team found that running Infer in CI without tuning produced a 70%+ false-positive rate on new Android code. Instead of abandoning the tool, they built a differential mode — Infer only reported bugs that were new in the diff, not pre-existing in the codebase. This reduced alert volume dramatically and maintained developer trust.

Google's internal experience, described in the 2018 paper "Lessons from Building Static Analysis Tools at Google" (Sadowski et al., CACM), similarly concluded that warnings must be actionable, accurate, and timely to be adopted. Their FindBugs successor tooling required 90%+ true-positive rate before a check was enabled by default.

In Lesson 2 we will examine how to configure and enforce ESLint, Pylint, and SonarQube in real team environments — including how to write custom rules and manage rule inheritance across monorepos.

Lesson 1 Quiz

What Linters and Static Analyzers Do
1. What fundamental technique do linters use to analyze code without executing it?
Correct. Linters parse source into an AST and apply rules to that tree structure — no execution involved.
Not quite. Linters parse the source code into an abstract syntax tree and inspect that structure for rule violations, without running the program.
2. The Heartbleed vulnerability (CVE-2014-0160) is used as a case study because post-hoc analysis showed it would have been flagged by which tools running in CI?
Correct. CMU CyLab researchers confirmed Coverity Scan and Klocwork would have flagged the unvalidated memcpy size.
Incorrect. ESLint and Pylint are JavaScript/Python linters with no C analysis capability. The deep C static analyzers Coverity Scan and Klocwork are the documented tools.
3. Facebook's Infer team solved the false-positive problem in CI by implementing which approach?
Correct. Differential analysis reports only new issues introduced by the current change, avoiding noise from pre-existing issues.
Incorrect. Facebook built a differential mode that limited Infer output to bugs introduced in the current diff, not all pre-existing issues in the codebase.
4. According to the Google "Lessons from Building Static Analysis Tools" paper (Sadowski et al., 2018), what true-positive rate was required before a check was enabled by default?
Correct. Google required 90%+ true-positive rate before enabling a check by default in their internal tools.
Incorrect. Google's documented threshold was 90%+ true-positive rate — below that, the noise eroded developer trust enough to render the check counterproductive.

Lab 1 — Diagnosing Tool Fit

AI Practice · Static Analysis Tool Selection

Scenario

Your team maintains a Python/TypeScript monorepo with 300k lines of code. You are evaluating which static analysis tools to add to different stages of your CI/CD pipeline. You need to justify your recommendations to a skeptical engineering manager who is worried about build-time impact and false positives.

Ask the AI assistant about tool selection strategy, the tradeoffs between linters vs. deep static analyzers, how to handle false-positive rate concerns, and how to structure tool gates at different pipeline stages. Aim for at least 3 exchanges to complete the lab.
AI Lab Assistant
Static Analysis Advisor
Welcome to Lab 1. I'm your static analysis advisor. You're evaluating tools for a Python/TypeScript monorepo. What aspect would you like to tackle first — tool categories and their pipeline placement, how to handle false-positive concerns with management, or something else?
Module 3 · Lesson 2

Configuring ESLint, Pylint, and SonarQube

Making tools do exactly what your team needs — and enforcing that they actually run.
What does a production-grade linter configuration look like, and how do teams prevent configuration drift across a growing codebase?

In 2015, Airbnb Engineering published eslint-config-airbnb on npm, encoding their internal JavaScript style guide as a sharable ESLint configuration. It became one of the most downloaded npm packages in history — consistently above 30 million weekly downloads by 2023. The reason was not Airbnb's brand but a practical reality: most teams lacked the time to build a principled rule set from scratch, and Airbnb's configuration represented documented, deliberated decisions rather than arbitrary defaults.

ESLint: Configuration Architecture

ESLint's flat config system (default from v9, introduced in v8.21) uses eslint.config.js instead of .eslintrc. The key architectural decisions a team must make are: which rule sets to extend, which plugins to require, and how to handle monorepo overrides.

// eslint.config.js — production monorepo example import js from '@eslint/js'; import airbnb from 'eslint-config-airbnb'; import tsPlugin from '@typescript-eslint/eslint-plugin'; import tsParser from '@typescript-eslint/parser'; import security from 'eslint-plugin-security'; export default [ js.configs.recommended, { files: ['**/*.ts', '**/*.tsx'], languageOptions: { parser: tsParser }, plugins: { '@typescript-eslint': tsPlugin, security }, rules: { 'no-console': 'error', '@typescript-eslint/no-explicit-any': 'error', 'security/detect-sql-injection': 'warn', '@typescript-eslint/explicit-function-return-type': 'warn' } }, { files: ['packages/legacy/**'], rules: { // Relax rules for legacy code — document why '@typescript-eslint/no-explicit-any': 'warn' } } ];

The critical practice is the legacy override block. When inheriting a codebase, enabling strict rules globally will flood the team with warnings on code no one is actively changing. The pattern of creating a legacy/** glob with relaxed rules — documented with a comment explaining why — lets teams tighten standards incrementally without blocking current work.

Pylint: Configuration and Custom Checkers

Pylint uses a .pylintrc or pyproject.toml section. Its most important configuration decision is the disable list: which checks to silence. Teams that disable checks without documented rationale create invisible technical debt.

# pyproject.toml — Pylint configuration [tool.pylint.main] jobs = 4 # parallel workers fail-under = 8.5 # CI fails if score drops below 8.5/10 load-plugins = ["pylint_django", "pylint.extensions.docparams"] [tool.pylint.messages_control] # Documented disables — each must have a JIRA ticket in comment disable = [ "C0301", # line-too-long: enforced by black formatter instead "R0903", # too-few-public-methods: intentional for data classes "W0511", # fixme: tracked in issue tracker, not a blocker ] [tool.pylint.design] max-args = 7 max-returns = 4 max-branches = 12

The fail-under threshold is a powerful enforcement mechanism. By setting a minimum score (Pylint scores 0–10), CI will break if code quality degrades below the baseline. The threshold should be set at the current project score, then raised incrementally as quality improves — never lowered without a documented decision.

SonarQube: Quality Gates and the Leak Period

SonarQube's most important concept for teams adopting it on existing codebases is the New Code Period (formerly called the "leak period"). Rather than requiring teams to fix all existing technical debt before shipping, SonarQube gates only on issues introduced in the current branch. This is conceptually identical to Facebook Infer's differential mode — focus enforcement on new code, track old debt separately.

The default SonarQube Quality Gate ("Sonar way") fails a build if new code has: fewer than 80% coverage, any new bugs rated A–C, or any new security hotspots unreviewed. Teams typically customize this — the important governance practice is that any loosening of the Quality Gate requires an Architecture Decision Record (ADR), not just a config change.

Documented Practice — Spotify Engineering

Spotify's engineering blog (2020) described their SonarQube rollout across 150+ microservices: they ran SonarQube in "advisory" mode for 90 days before making it a hard gate, using the period to establish baselines and train teams on interpreting findings. Hard enforcement without a grace period caused developer resistance in early pilots.

Enforcing Configuration: The Shared Config Package

Configuration drift occurs when different teams in a monorepo maintain separate .eslintrc or .pylintrc files that diverge over time. The solution used by large organizations (Google, Meta, Stripe) is to publish a single internal npm/PyPI package — e.g., @company/eslint-config — that contains the canonical rules. Teams extend from this package and can only add rules, never remove them. The config package itself is reviewed by the platform team before changes are merged.

Team Standard

Every disabled lint rule must have a comment with three things: the rule ID, the reason it is disabled, and a reference to a ticket or ADR. Silent suppressions — // eslint-disable-next-line with no explanation — are themselves a lint violation in well-run codebases.

Lesson 2 Quiz

Configuring ESLint, Pylint, and SonarQube
1. What is the primary purpose of a glob-based override block (e.g., packages/legacy/**) in an ESLint configuration?
Correct. The pattern allows incremental tightening — strict on new code, temporarily relaxed on legacy code — without blocking current work.
Incorrect. The override applies relaxed (not absent) rules to legacy paths, allowing teams to raise standards incrementally without being paralyzed by inherited debt.
2. Pylint's fail-under configuration option does what in a CI pipeline?
Correct. fail-under sets a minimum acceptable Pylint score (0–10); falling below it fails the CI run.
Incorrect. fail-under sets a minimum Pylint score threshold; the CI job exits non-zero if the project scores below that number.
3. SonarQube's "New Code Period" concept is designed to solve which specific problem?
Correct. The New Code Period focuses enforcement on newly introduced issues, making adoption feasible on codebases with pre-existing debt.
Incorrect. The New Code Period solves the adoption problem: teams with existing debt cannot remediate everything before shipping. Gating on new code only makes quality enforcement viable immediately.
4. What is the governance requirement for a properly documented lint rule suppression?
Correct. Rule ID + reason + ticket/ADR reference. Silent suppressions are themselves a policy violation in well-governed codebases.
Incorrect. Every suppression should document: which rule (ID), why it is suppressed (reason), and where the decision is tracked (ticket or ADR). Silent disables accumulate as invisible debt.

Lab 2 — ESLint and Pylint Configuration Review

AI Practice · Linter Configuration Decisions

Scenario

You've inherited a monorepo that has an .eslintrc with 12 silently-disabled rules (no comments) and a .pylintrc with fail-under = 5.0. Your task is to develop a remediation plan — what to document, what to raise, and how to structure a shared config package rollout.

Discuss the remediation approach with the AI assistant: how to audit existing suppressions, prioritize rule re-enablement, design a shared config package structure, and set a realistic fail-under improvement roadmap. Aim for at least 3 exchanges.
AI Lab Assistant
Linter Configuration Advisor
I'm ready to help you remediate this configuration situation. A Pylint fail-under of 5.0 and 12 undocumented ESLint suppressions are both significant red flags. Where would you like to start — auditing the ESLint suppressions, the Pylint score situation, or the shared config architecture?
Module 3 · Lesson 3

Integrating SAST into CI/CD Pipelines

Security analysis as a first-class engineering workflow, not a post-release audit.
What does a functional SAST integration look like in GitHub Actions or GitLab CI, and how do teams manage findings without blocking every deployment?

When GitHub made CodeQL available to all public repositories in September 2020 as part of GitHub Advanced Security, their internal data showed that 72% of critical vulnerabilities discovered by CodeQL in open-source projects were found in code that had passed all existing CI checks. The tool used semantic analysis — understanding what the code does, not just what it looks like — to trace paths from user input to dangerous sinks across multiple files and function calls.

SAST vs. Linters: What Changes at the Pipeline Stage

SAST (Static Application Security Testing) tools differ from linters in two ways that affect pipeline integration: they take significantly longer to run (minutes vs. seconds), and their findings require security expertise to triage. This means they cannot simply block PRs the way a linter does — a 10-minute CodeQL scan that fails every PR with a false positive will be disabled within a week.

The functional integration model used by most mature organizations has three components: fast rules block PRs (Semgrep patterns, Bandit, Brakeman on diff only), deep scans run on main branch nightly (CodeQL, Coverity full analysis), and findings are triaged into a security backlog with SLA-based remediation windows rather than immediate block requirements.

SAST Static Application Security Testing — analysis of source code to identify security vulnerabilities before execution, using data-flow and taint tracking.
Taint analysis Tracking the flow of untrusted (tainted) data from user-controlled sources to security-sensitive sinks (SQL queries, shell commands, HTML output).
Semgrep rule A pattern-matching rule written in YAML that describes a code pattern to flag — equivalent to a grep that understands code structure and types.
GitHub Actions: CodeQL and Semgrep Integration

The following represents a production-grade two-stage SAST setup: Semgrep runs fast checks on PR diffs, CodeQL runs deep analysis on every push to main.

# .github/workflows/sast.yml name: SAST Analysis on: pull_request: branches: [main, develop] push: branches: [main] schedule: - cron: '0 2 * * *' # Nightly deep scan at 02:00 UTC jobs: semgrep-pr: if: github.event_name == 'pull_request' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: { fetch-depth: 0 } - name: Semgrep diff scan uses: semgrep/semgrep-action@v1 with: config: > p/owasp-top-ten p/python p/typescript generateSarif: "1" env: SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }} # Only scan changed files — keep PR checks under 60s codeql-deep: if: github.event_name != 'pull_request' runs-on: ubuntu-latest permissions: security-events: write steps: - uses: actions/checkout@v4 - uses: github/codeql-action/init@v3 with: languages: python, javascript queries: security-extended - uses: github/codeql-action/autobuild@v3 - uses: github/codeql-action/analyze@v3 with: category: "/language:python,javascript"
Managing SAST Findings: The Triage SLA Model

The most common failure mode in SAST adoption is treating all findings as equally urgent. A documented triage model, aligned with CVSS severity, prevents alert fatigue while ensuring critical issues are addressed promptly. The model below is based on practices documented by OWASP's DevSecOps guidance and is consistent with how organizations like Netflix and Cloudflare handle SAST output.

Severity Example Finding Pipeline Action Remediation SLA
Critical SQL injection, RCE vector, hardcoded credential Block PR immediately Fix before merge
High XSS sink, path traversal, insecure deserialization Block PR; allow exception with security team sign-off 7 days
Medium Insecure random, missing TLS validation Advisory warning; tracked in backlog 30 days
Low / Info Verbose error messages, weak cipher preference Nightly report only Next quarter
Real Case — Equifax Breach Attribution (2017)

Post-breach analysis of the Equifax Apache Struts vulnerability (CVE-2017-5638) showed that the organization had SAST tooling deployed but findings were not being acted upon within SLA. A critical-severity finding on the specific Struts version was documented in their tracking system 68 days before exploitation. The tool worked; the process did not. Triage SLA enforcement is not optional infrastructure.

Writing Custom Semgrep Rules

Semgrep's power for teams is the ability to write organization-specific rules that encode your own security anti-patterns. The rule format is YAML with code pattern matching.

# rules/no-raw-sql.yml — detect raw SQL string concatenation rules: - id: no-raw-sql-concatenation languages: [python] severity: ERROR message: "Raw SQL with string concatenation detected. Use parameterized queries." metadata: cwe: "CWE-89" owasp: "A03:2021 - Injection" pattern-either: - pattern: cursor.execute("..." + $VAR) - pattern: cursor.execute(f"...{$VAR}...") - pattern: cursor.execute("..." % $VAR)
Integration Standard

Every SAST finding that is suppressed (marked as false positive or accepted risk) must be reviewed by a second person with security knowledge and logged with an expiration date. Accepted risks do not stay accepted indefinitely — they must be reviewed when the affected component is next modified.

Lesson 3 Quiz

Integrating SAST into CI/CD Pipelines
1. Why should deep SAST tools (like CodeQL full scans) generally NOT block every pull request?
Correct. A 10-minute blocking scan with high false-positive rates gets disabled or bypassed quickly — which is worse than not having it at all.
Incorrect. The problem is practical: long runtimes plus false positives on PRs create enough friction that teams route around the gate, defeating its purpose.
2. The Equifax breach case (2017) is cited as evidence of which specific failure in SAST workflows?
Correct. The tool identified the vulnerability; the process failure was not acting on the finding within the required remediation window.
Incorrect. The documented lesson from Equifax is that the SAST finding existed and was tracked — the failure was process: no one enforced the remediation SLA before the breach occurred.
3. What does taint analysis in a SAST tool specifically track?
Correct. Taint analysis follows untrusted data from sources (user input) to sinks (SQL queries, shell commands, HTML rendering) to detect injection vulnerabilities.
Incorrect. Taint analysis specifically tracks how data flows from user-controlled sources to dangerous sinks — the mechanism that detects SQL injection, XSS, and similar vulnerability classes.
4. According to the triage SLA model, what is the correct pipeline action for a High-severity SAST finding?
Correct. High severity blocks PRs but allows a security-reviewed exception path, with a 7-day remediation window.
Incorrect. High-severity findings block the PR but include a security team exception path (unlike Critical which has none), and must be remediated within 7 days.

Lab 3 — SAST Pipeline Design

AI Practice · CI/CD Security Integration

Scenario

You are designing the SAST integration for a fintech startup's GitHub Actions pipeline. The codebase is Python/Node.js, handles payment card data, and must demonstrate compliance with PCI DSS requirements. Your engineering manager wants security checks but cannot tolerate broken pipelines on routine commits.

Work with the AI assistant to design the multi-stage SAST integration: which tools at which stages, how to structure the triage SLA policy, how to write a custom Semgrep rule for a PCI-specific concern (e.g., detecting logging of card numbers), and how to document the pipeline for a compliance audit. Aim for at least 3 exchanges.
AI Lab Assistant
SAST Pipeline Advisor
Welcome to the SAST pipeline design lab. A fintech PCI DSS context adds specific requirements — PCI DSS Requirement 6.3.2 mandates an inventory of bespoke code reviewed for vulnerabilities prior to production. Let's design a pipeline that satisfies that requirement without destroying developer velocity. Where shall we start: tool selection, triage policy, or the custom Semgrep rules?
Module 3 · Lesson 4

Custom Rules and Team-Wide Enforcement

When standard rule sets aren't enough — encoding your team's specific knowledge into tooling.
How do you write custom lint rules that encode domain-specific knowledge, and what governance ensures those rules stay current?

Stripe's engineering team described their progressive TypeScript adoption in a 2021 blog post: they wrote a custom ESLint rule that flagged any use of TypeScript's escape hatchesany, @ts-ignore, and type assertions without documented justification — in files above a certain test coverage threshold. This created a self-reinforcing system: well-tested code had to be well-typed; legacy code could still use escape hatches. The rule was written by the developer tools team and published to their internal ESLint config package, automatically applying to all repositories on next version bump.

When to Write a Custom Rule

Custom rules are justified when: (1) a pattern appears in code review feedback repeatedly and reviewers must catch it manually; (2) the pattern is domain-specific enough that public rule sets don't cover it; (3) the fix is mechanical enough that automation is reliable. The signal that a custom rule is warranted is when the same review comment appears three or more times in a sprint across different PRs.

Custom rules are not the right tool for architectural decisions, design judgment, or anything that requires context unavailable from the syntax tree. "Don't build this feature this way" is a conversation, not a lint rule.

Writing an ESLint Custom Rule: AST Visitor Pattern

ESLint rules are Node.js modules that export an object with a create method returning an AST visitor. The visitor is a map from AST node types to handler functions. The following example flags direct usage of Math.random() in files under a src/crypto/ path, where cryptographically secure randomness is required.

// rules/no-math-random-in-crypto.js module.exports = { meta: { type: 'error', docs: { description: 'Disallow Math.random() in cryptographic contexts', url: 'https://wiki.internal/rules/no-math-random-in-crypto' }, messages: { avoidMathRandom: 'Use crypto.getRandomValues() or crypto.randomUUID() ' + 'in cryptographic contexts. Math.random() is not CSPRNG.' } }, create(context) { const filename = context.getFilename(); if (!filename.includes('src/crypto/')) return {}; return { // Visitor for CallExpression nodes CallExpression(node) { if ( node.callee.type === 'MemberExpression' && node.callee.object.name === 'Math' && node.callee.property.name === 'random' ) { context.report({ node, messageId: 'avoidMathRandom' }); } } }; } };
Writing a Custom Pylint Checker

Pylint's checker system is based on AST visitors using Python's ast module. Custom checkers are registered as plugins via load-plugins in .pylintrc. The following checker flags any function that both writes to a logger and returns a value — a pattern that, in the team's codebase, indicated that side effects were being hidden in utility functions.

# checkers/no_logging_return.py from pylint.checkers import BaseChecker from astroid import nodes class NoLoggingReturnChecker(BaseChecker): name = 'no-logging-return' msgs = { 'W9001': ( 'Function logs and returns a value — consider separating concerns', 'logging-return-mixed', 'Functions should not both log and return meaningful values. ' 'See ADR-0042 for the logging architecture decision.' ) } def visit_functiondef(self, node): has_log = any( isinstance(child, nodes.Expr) and isinstance(getattr(child, 'value', None), nodes.Call) and 'log' in getattr( getattr(child.value, 'func', None), 'attrname', '' ) for child in node.body ) has_return = any( isinstance(child, nodes.Return) and child.value for child in node.body ) if has_log and has_return: self.add_message('logging-return-mixed', node=node) def register(linter): linter.register_checker(NoLoggingReturnChecker(linter))
Governance: The Custom Rule Lifecycle

Custom rules accumulate technical debt of their own if not governed. Teams at Amazon and Google have documented a lifecycle for internal lint rules: proposal (3+ repeated review comments trigger consideration), review (platform team evaluates false-positive risk), staging (warning-only for 2 weeks on main branch), enforcement (promoted to error), and retirement (rule is removed when the underlying pattern is eliminated from the codebase).

The retirement phase is frequently neglected. Rules that no longer apply to the codebase add to build time and confuse new engineers who cannot understand why a pattern is forbidden. Quarterly audits of custom rule trigger counts — rules with zero violations in 90 days are candidates for retirement — prevent this accumulation.

Phase Trigger Duration Outcome
Proposal 3+ repeated review comments on same pattern 1–2 days Rule RFC submitted to platform team
Review Platform team evaluates FP risk and scope Up to 1 week Approved or rejected with rationale
Staging Rule merged as warning-only 2 weeks FP rate measured; developers notified
Enforcement FP rate below threshold; no objections Ongoing Rule promoted to error; blocks CI
Retirement Zero violations for 90 days 1 sprint Rule removed; ADR updated
Enforcement Reality

The most powerful enforcement mechanism for team-wide linting standards is not CI gates — it's editor integration. When ESLint or Pylint runs in developers' editors via LSP and highlights violations as they type, issues are caught before they're even committed. Pre-commit hooks and CI are the backstop, not the primary defense. Invest in developer tooling documentation and onboarding scripts alongside rule authorship.

This completes the four lessons of Module 3. Proceed to the Module Test to validate your understanding across all topics: tool categories, configuration governance, SAST pipeline integration, and custom rule authorship.

Lesson 4 Quiz

Custom Rules and Team-Wide Enforcement
1. According to the lesson, what is the primary signal that a custom lint rule is warranted rather than a code review comment?
Correct. Repeated manual review comments on the same pattern are the signal — automation prevents the repetition and scales the institutional knowledge.
Incorrect. The trigger is repetition: when reviewers are catching the same mechanical pattern repeatedly across different PRs, a lint rule encodes and enforces that knowledge automatically.
2. In ESLint's custom rule API, what does the create(context) method return?
Correct. The visitor maps AST node types (e.g., "CallExpression") to functions that are called when ESLint traverses nodes of that type.
Incorrect. create(context) returns an AST visitor object — keys are AST node type names, values are handler functions called when nodes of that type are encountered during tree traversal.
3. What is the purpose of the "staging" phase in the custom rule lifecycle?
Correct. Warning-only staging for two weeks reveals false-positive rates and gives developers time to adjust before the rule blocks CI.
Incorrect. The staging phase deploys the rule as a warning (not an error) on main for approximately two weeks, measuring real-world false-positive rates before promoting to blocking enforcement.
4. Why should teams audit custom rules quarterly for zero-violation rules?
Correct. Rules with no violations add build time and confuse engineers — if the pattern is gone from the codebase, the rule has done its job and should be retired.
Incorrect. Zero-violation rules accumulate over time, slowing builds and confusing new engineers who cannot find the pattern being forbidden. Retirement of successful rules is part of good rule governance.

Lab 4 — Custom Rule Design Workshop

AI Practice · Writing and Governing Custom Lint Rules

Scenario

Your team uses Django (Python) and React (TypeScript). Two patterns keep appearing in code review: (1) Django views that call print() instead of using the logger, and (2) React components that use dangerouslySetInnerHTML without a corresponding JSDoc comment linking to the security review ticket. You need to design custom rules for both and define their lifecycle governance.

Work with the AI to design the custom rule logic for both patterns, discuss the AST nodes involved, plan the staging approach, and define what "retirement criteria" would look like for each rule. Aim for at least 3 exchanges.
AI Lab Assistant
Custom Rule Design Advisor
Welcome to the custom rule design lab. You have two real patterns to encode: Django print() usage in views, and undocumented dangerouslySetInnerHTML in React. Both are good candidates for automation. Which would you like to design first, and do you want to start with the AST structure, the rule logic, or the governance lifecycle?

Module 3 Test

Automated Linting and Static Analysis · 15 Questions · Pass at 80%
1. The term "lint" originates from a tool written at Bell Labs in 1978. What language did it analyze?
Correct. Stephen Johnson's lint tool analyzed C code for suspicious constructs at Bell Labs in 1978.
Incorrect. The original lint tool was written by Stephen Johnson at Bell Labs to flag suspicious C constructs.
2. What is the key operational difference between a linter and a deep static analyzer like Coverity?
Correct. Speed and interprocedural scope are the key differences — linters are fast and file-scoped; deep analyzers are slower and reason across the whole program.
Incorrect. The key differences are speed and analysis scope: linters parse ASTs within files in milliseconds; deep static analyzers perform inter-procedural, whole-program analysis over minutes.
3. After Heartbleed was disclosed in 2014, what specific change did the OpenSSL Foundation make to its development process?
Correct. The OpenSSL Foundation added mandatory Coverity scanning to their commit pipeline after Heartbleed.
Incorrect. The documented response was mandatory Coverity static analysis scanning on every commit — the same class of tool that would have caught the original vulnerability.
4. Google's internal static analysis team (Sadowski et al., 2018) identified three properties warnings must have to be adopted by developers. Which set is correct?
Correct. Actionable, accurate, and timely — the three properties Google required for developer adoption of static analysis warnings.
Incorrect. Google's paper specified that warnings must be actionable (developer knows what to do), accurate (high true-positive rate), and timely (surfaced close to when the code was written).
5. ESLint's flat config system (default from v9) uses which configuration file?
Correct. ESLint's flat config uses eslint.config.js, replacing the legacy .eslintrc format.
Incorrect. The flat config format introduced in ESLint v8.21 and made default in v9 uses eslint.config.js, superseding all .eslintrc variants.
6. What governance rule applies to every inline lint suppression (e.g., // eslint-disable-next-line) in a well-governed codebase?
Correct. Silent suppressions are themselves a lint violation. Every suppression must document why it exists and where the decision is tracked.
Incorrect. Suppressions are allowed but must be documented: the rule being suppressed, why it is being suppressed, and a reference (ticket or ADR) tracking the underlying decision.
7. Pylint's fail-under threshold should never be lowered without what?
Correct. Lowering a quality threshold is a governance decision that must be documented — otherwise it silently encodes a regression in standards.
Incorrect. Lowering fail-under is a policy regression and must be documented as a deliberate decision with rationale, not made silently in a config change.
8. SonarQube's default "Sonar way" Quality Gate fails a build on new code under which condition regarding coverage?
Correct. SonarQube gates on new code coverage below 80%, not the overall project coverage — consistent with the New Code Period concept.
Incorrect. The "Sonar way" gate applies the 80% threshold to new code only, not the full project — this is the New Code Period approach to adoption in legacy codebases.
9. The two-stage SAST pipeline pattern (fast check on PR, deep scan nightly) maps respectively to which tools in the lesson's GitHub Actions example?
Correct. Semgrep's fast diff-only scan on PRs; CodeQL's deep semantic analysis on main branch pushes and nightly schedule.
Incorrect. The lesson's pipeline example uses Semgrep (fast, diff-only) for PR gates and CodeQL (deep semantic analysis) for nightly and main-branch pushes.
10. Under the triage SLA model, a medium-severity SAST finding should be remediated within how many days?
Correct. Medium severity: advisory warning, tracked in backlog, 30-day SLA.
Incorrect. The triage model assigns 30 days to medium-severity findings (7 days for High, fix-before-merge for Critical).
11. A Semgrep rule's pattern-either key does what?
Correct. pattern-either is a logical OR — a match on any one of the listed patterns triggers the rule.
Incorrect. pattern-either acts as logical OR: the rule fires if the code matches any one of the listed patterns, allowing multiple equivalent anti-patterns to be covered by one rule.
12. Which real company's ESLint configuration became one of npm's most downloaded packages (30M+ weekly downloads by 2023), demonstrating the value of shareable rule sets?
Correct. eslint-config-airbnb became a widely adopted baseline because it encoded deliberated decisions in a reusable package.
Incorrect. Airbnb published eslint-config-airbnb in 2015, which reached 30M+ weekly downloads — demonstrating that well-documented, shareable rule sets solve a universal team problem.
13. In ESLint's custom rule API, what is the purpose of the meta.messages object?
Correct. meta.messages maps messageId keys to message template strings, allowing context.report() to reference them by ID rather than inlining strings.
Incorrect. meta.messages defines named message templates (messageId → string) that context.report() references by ID, separating message content from reporting logic.
14. What does the custom rule "staging" phase specifically measure before enforcement is enabled?
Correct. Warning-only staging reveals whether the rule fires on legitimate code (false positives) before it has the authority to break CI.
Incorrect. The staging phase deploys the rule as a warning to measure its false-positive rate in the real codebase before it can block any CI runs.
15. Which of the following best describes why editor integration (via LSP) is considered more powerful than CI gate enforcement for lint rules?
Correct. The fastest feedback loop is in the editor — catching violations before a commit eliminates the round-trip cost of CI failure entirely.
Incorrect. The power of editor integration is immediacy: developers see violations as they type, at the lowest cost of correction. CI gates catch what slips past the editor but introduce a much longer feedback loop.