JustAppSec
Back to guides

Cyber Essentials for Development Teams

Cyber Essentials is the UK Government's baseline security scheme. It defines five technical controls that protect against the most common internet-based attacks. Most guidance is written for IT administrators - this guide translates each control into concrete actions for development teams shipping software.

If your organisation is in a UK Government supply chain, or sells to enterprises that require Cyber Essentials certification, these controls are not optional. As mandating Cyber Essentials in procurement frameworks expands, understanding what they mean for your codebase and pipelines matters now.

Reference: NCSC - Cyber Essentials Reference: Cyber Essentials Requirements (PDF)

The five controls

ControlIT framingDeveloper framing
FirewallsBoundary firewalls and internet gatewaysNetwork segmentation, ingress rules, WAF/CDN config
Secure configurationRemove unnecessary software, change defaultsHarden container images, remove debug endpoints, rotate default credentials
User access controlLeast privilege, individual accountsRBAC/ABAC in application code, service account scoping, MFA
Malware protectionAnti-malware softwareDependency scanning, container scanning, CI security gates
Security update managementPatch within 14 days for criticalAutomated dependency updates, patch SLAs, image rebuild pipelines

Control 1: Firewalls - boundary protection

For development teams this means:

  • Production services are not directly exposed to the internet without a reverse proxy, CDN, or load balancer
  • Only necessary ports are open - no SSH, database, or admin ports are publicly accessible
  • Infrastructure-as-code templates explicitly define security groups and ingress rules
  • Development and staging environments are not publicly accessible (use VPN or IP allowlisting)
  • Cloud provider firewalls (AWS Security Groups, GCP Firewall Rules, Azure NSGs) are reviewed in code review
# Terraform - explicit ingress, deny by default
resource "aws_security_group" "web" {
  ingress {
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
  # No other ingress - SSH via SSM, not port 22
}

Common failure: Leaving database ports (3306, 5432, 27017) open in development security groups that get promoted to production.

Control 2: Secure configuration

For development teams this means:

  • Default admin credentials are changed or removed before deployment
  • Debug modes, verbose error pages, and development endpoints are disabled in production
  • .env files, docker-compose.override.yml, and development configs are not deployed
  • Container images use minimal base images (distroless, Alpine) with no unnecessary packages
  • Server headers that leak technology versions are suppressed (X-Powered-By, Server)
  • Auto-generated admin routes (e.g. /admin, /graphql/playground) are gated or removed
# Minimal production image - no shell, no package manager
FROM gcr.io/distroless/nodejs22-debian12
COPY --from=build /app/dist /app
CMD ["server.js"]

Common failure: Shipping a Next.js or Rails app with development error pages that leak stack traces and file paths.

Control 3: User access control

For development teams this means:

  • Application authentication enforces MFA where supported (or delegates to an IdP that does)
  • Authorisation checks happen server-side on every request, not just in the UI
  • Service accounts use scoped credentials with the minimum permissions needed
  • CI/CD runners use short-lived, scoped tokens - not long-lived personal access tokens
  • Admin access to production systems is logged and uses separate credentials from day-to-day accounts
  • Default accounts and API keys shipped by frameworks or dependencies are removed
// Server-side authorisation check - not just a frontend guard
export async function updateProject(projectId: string, data: UpdateData) {
  const user = await getAuthenticatedUser();
  const project = await db.project.findUnique({ where: { id: projectId } });

  if (project.ownerId !== user.id && !user.roles.includes("admin")) {
    throw new ForbiddenError("Not authorised to update this project");
  }

  return db.project.update({ where: { id: projectId }, data });
}

Common failure: CI pipelines using a single shared token with write access to every repository and deployment target.

Control 4: Malware protection

Traditional malware protection means antivirus. For development teams, the equivalent is automated scanning for known-bad code in your dependencies and containers:

  • Software Composition Analysis (SCA) runs in CI on every pull request
  • Container images are scanned for known vulnerabilities before deployment
  • A clear exceptions/waiver process exists for dependencies with known CVEs that cannot be immediately updated
  • Lock files (package-lock.json, pnpm-lock.yaml, poetry.lock) are committed and reviewed
  • Dependency sources are pinned to trusted registries - no unvetted third-party mirrors
# GitHub Actions - SCA on every PR
- name: Audit dependencies
  run: npm audit --audit-level=high
  
- name: Scan container image
  uses: aquasecurity/trivy-action@master
  with:
    image-ref: ${{ env.IMAGE }}
    severity: CRITICAL,HIGH
    exit-code: 1

Common failure: SCA is enabled but generates so many alerts that the team ignores them. Set a severity threshold and maintain a reviewed exceptions list.

Control 5: Security update management

Cyber Essentials requires critical patches to be applied within 14 days. For development teams:

  • Automated dependency update tools (Dependabot, Renovate) are enabled and PRs are reviewed promptly
  • Container base images are rebuilt regularly - at least weekly - to pick up OS-level patches
  • An emergency deployment path exists to push critical security fixes to production within days, not weeks
  • End-of-life runtimes and frameworks are tracked and upgraded before support expires
  • The team has a documented SLA: critical CVEs patched within 14 days, high within 30 days
# Renovate - auto-merge patch updates, flag breaking changes
{
  "extends": ["config:recommended"],
  "packageRules": [
    {
      "matchUpdateTypes": ["patch"],
      "automerge": true
    }
  ]
}

Common failure: Base images pinned to a specific tag that was last updated 18 months ago.

Mapping to your CI/CD pipeline

A practical way to verify Cyber Essentials alignment is to check your pipeline:

Pipeline stageCE controlAutomated check
PR openedMalware protectionSCA scan, container scan
PR openedSecure configurationLint for debug flags, default credentials
Pre-mergeUser access controlCODEOWNERS review, scoped deploy tokens
BuildSecure configurationMinimal base image, no dev dependencies
DeployFirewallsIaC security group validation
ScheduledSecurity updatesDependency update PRs, image rebuild

Cyber Essentials Plus

Cyber Essentials Plus adds a hands-on technical audit on top of the self-assessment. For development teams, this means an assessor will verify that:

  • Patching is actually happening (not just configured)
  • Access controls are enforced in practice
  • Scanning tools are generating actionable results

If you are preparing for Plus, ensure your CI dashboards can demonstrate compliance history - green builds, resolved dependency alerts, and patch deployment dates.

Further reading


Content is AI-assisted and reviewed by our team, but issues may be missed and best practices evolve rapidly, send corrections to [email protected]. Always consult official documentation and validate key implementation decisions before making design or security choices.

Need help?Get in touch.