Skip to main content
Blog

Cybersecurity Best Practices for Software Teams

Last updated AppSec

Most breaches are not clever. They are a leaked credential in a repository, a dependency nobody updated, an internal service exposed to the internet by accident, or an endpoint that trusted a parameter it should have checked. The attacks that make headlines are sophisticated; the attacks that actually happen to ordinary software teams are almost always the result of something mundane that everyone knew about and nobody owned.

This guide is about the mundane things, done properly. It covers how to think about threats without a security background, the controls that stop the largest share of real attacks, how to build security into a delivery pipeline rather than bolting it on, and what to do when something goes wrong. It is written for engineers and tech leads, not for security specialists.


What you will learn
  • A threat modelling method you can run in an hour with no training
  • The handful of controls that prevent most real-world compromise
  • How to handle secrets, dependencies and supply chain risk
  • Authentication and authorisation mistakes that keep recurring
  • Security in the pipeline: what to automate and what to gate
  • Incident response for teams without a security operations centre
In this article
  1. How attacks actually happen
  2. Threat modelling in an hour
  3. Identity and access
  4. Authorisation: the bug class nobody scans for
  5. Secrets management
  6. Dependencies and the supply chain
  7. Input handling and injection
  8. Data protection
  9. Infrastructure and configuration
  10. Logging, monitoring and detection
  11. Security in the delivery pipeline
  12. Incident response without a security team
  13. Twelve recurring mistakes
  14. A security baseline you can implement in a quarter
  15. Frequently asked questions

1. How attacks actually happen

Before choosing controls, it helps to know what you are defending against. Real compromise of ordinary business software follows a small number of paths, and they are stubbornly consistent year after year.

PathTypical entryWhat stops it
Credential compromisePhishing, reused password, leaked tokenMulti-factor authentication, short-lived credentials
Vulnerable dependencyA known flaw in a library nobody updatedAutomated updates, dependency scanning
Exposed serviceA database or admin panel reachable from the internetDefault-deny networking, configuration scanning
Broken authorisationChanging an identifier in a request to read someone else's dataServer-side checks on every access
InjectionUntrusted input reaching an interpreterParameterised queries, output encoding
Misconfigured storageA publicly readable bucketPreventive policy, not detection
Insider or third partyExcessive access granted and never revokedLeast privilege plus periodic review

Two observations follow. First, almost none of these require the attacker to find a novel vulnerability — they require them to find something you forgot. Second, the defences are unglamorous and mostly automatable, which is good news: security for most teams is an operational discipline problem rather than a research problem.

The useful mental model: an attacker is looking for the cheapest path in. Your job is not to be impenetrable — it is to make every available path more expensive than the value behind it, and to notice when someone is trying.

2. Threat modelling in an hour

Threat modelling has a reputation for being a formal, specialist activity. It does not need to be. A team can produce most of the value in a single session with a whiteboard by answering four questions.

What are we building?

Draw the system: components, data stores, and the boundaries between things you control and things you do not. Mark every point where data crosses a trust boundary — from the internet into your system, from your system to a third party, from one tenant's context to another. Attacks happen at boundaries.

What can go wrong?

Walk each boundary and ask six questions: could someone pretend to be someone else here, could they tamper with the data, could they deny having done it, could they read something they should not, could they overwhelm it, could they gain more privilege than intended. Those six categories cover the large majority of realistic threats and require no specialist vocabulary.

What are we going to do about it?

For each credible threat, choose one of four responses: mitigate it with a control, eliminate it by removing the feature, transfer it to someone better placed to handle it, or accept it explicitly with a named owner. The last option is legitimate — what is not legitimate is leaving it undecided.

Did we do a good job?

Turn the mitigations into tickets with owners, and revisit the model when the architecture changes materially. A threat model that lives in a document nobody opens has produced a nice afternoon and nothing else.

Run this when starting a new service, when adding a new trust boundary, or when handling a new class of sensitive data. An hour spent here reliably finds issues that would otherwise be discovered by a penetration test six months later, at considerably greater cost.

3. Identity and access

Identity is where most real compromise begins, which makes it the highest-return area to get right.

  • Multi-factor authentication everywhere, with no exceptions for administrators — they are the highest-value targets. Prefer phishing-resistant methods over one-time codes where you can; codes can be relayed by a convincing fake login page.
  • Single sign-on for everything internal. Each system with its own account store is a separate place a departing employee retains access, and a separate password to be reused elsewhere.
  • Short-lived credentials for machines. Long-lived API keys are the credential most likely to leak and least likely to be rotated. Workload identity that issues tokens valid for minutes removes an entire class of incident.
  • Session handling that assumes compromise. Reasonable expiry, absolute lifetime limits, invalidation on password change, and the ability to terminate all sessions for a user immediately.
  • Joiners, movers, leavers as a process. Access granted for a project and never revoked accumulates until an average employee can reach far more than their role requires. A quarterly review with managers removing what is no longer needed is unglamorous and effective.

4. Authorisation: the bug class nobody scans for

Authentication asks who you are; authorisation asks what you may do. Scanners find injection and outdated libraries reasonably well. They cannot find a missing authorisation check, because only your business logic knows whether a given user should see a given record. This is why broken access control has been the most prevalent serious web vulnerability for years running.

The characteristic bug is simple: an endpoint accepts an identifier and returns the corresponding record without checking whether the caller is entitled to it. Change the number, get someone else's data. Variants include hidden interface elements that stop nothing because the endpoint is still callable, and administrative functions protected only by an unlinked URL.

Three defences, in order of effectiveness:

Enforce in a shared layer, not per handler. If every endpoint author must remember to add the check, one will forget, and that one is the vulnerability. Scope queries by the caller's tenant and permissions in a common data-access layer, so the default behaviour is safe and the unsafe path requires deliberate effort.

Make identifiers unguessable where practical. Random identifiers are not access control, but they remove trivial enumeration and turn a scripted attack into a targeted one.

Test for it explicitly. Write automated tests that attempt to access another tenant's data through every significant endpoint, and run them on every deployment. This is one of the highest-value test suites a multi-tenant application can have, and almost nobody writes it.

5. Secrets management

A secret is anything that grants access: passwords, API keys, tokens, certificates, connection strings. The rules are few and frequently broken.

  • Never in source control. Once committed, a secret is in the history forever, on every clone, including the laptop of someone who left last year. Removing it requires rewriting history and rotating the secret regardless.
  • Scan for them automatically, both before commit and across the existing repository. Leaked credentials in public repositories are found by automated scanners within minutes of being pushed.
  • Use a secrets manager, injected at runtime rather than baked into images or configuration files. Environment variables are acceptable; environment variables committed to a repository are not.
  • Rotate on a schedule and on departure. A rotation process that has never been exercised will not work when you need it during an incident.
  • Assume any secret that has ever been logged is compromised. Secrets in log files are common, searchable and retained for years.

When a secret does leak, the correct response is always the same and always immediate: rotate first, investigate second. Teams that investigate first, hoping the exposure was harmless, routinely discover it was not.

6. Dependencies and the supply chain

Modern applications are mostly other people's code. A typical service pulls in hundreds of packages transitively, any of which can contain a known vulnerability or, occasionally, deliberate malice.

Know what you ship. Generate a software bill of materials during the build. When a serious vulnerability is announced, the first question is whether you are affected, and answering it in minutes rather than days is the difference between a controlled response and a scramble.

Automate updates. Small, frequent dependency bumps applied automatically for patch versions, with tests as the gate. Teams that batch updates quarterly face large, risky upgrades and consequently defer them further — the deferral is the actual risk.

Prioritise by exploitability, not by severity score. A critical vulnerability in a component you do not use is less urgent than a moderate one in your authentication path. Reachability analysis, where available, cuts the noise dramatically and prevents alert fatigue from making the whole programme worthless.

Vet what you add. Before introducing a dependency, check that it is maintained, widely used and does something you could not reasonably write. A package with one maintainer and three hundred downloads a week is a supply chain risk regardless of how convenient it is.

Protect the build. Your build pipeline has access to source, secrets and production. It is a high-value target and should be treated like production: restricted access, reviewed changes to pipeline definitions, pinned build tooling, and no ability for an untrusted pull request to execute code with production credentials.

7. Input handling and injection

Injection happens when untrusted input reaches an interpreter that treats part of it as instructions. The interpreter varies — a database, a shell, a template engine, a browser — and so does the defence, but the principle is constant: separate data from instructions structurally rather than by trying to clean the data.

ContextCorrect defenceWhat does not work
Database queriesParameterised statements, alwaysEscaping quotes by hand
HTML outputContext-aware encoding by the template engineBlocklisting script tags
Shell commandsAvoid the shell; pass arguments as an arrayQuoting user input into a command string
File pathsResolve and verify the path stays inside the allowed rootRemoving "../" sequences
URLs fetched server-sideAllowlist of permitted destinationsBlocking known-internal addresses
DeserialisationDo not deserialise untrusted data into arbitrary typesFiltering class names

Validate input at the boundary against an allowlist of what is acceptable — expected type, length, format, range — and reject anything else. Allowlists age well; blocklists do not, because they enumerate the attacks you have thought of.

Server-side request forgery deserves specific mention because it has become one of the most damaging modern vulnerabilities. If your application fetches a URL supplied by a user, an attacker can point it at internal services or cloud metadata endpoints that trust anything on the local network. Restrict outbound destinations explicitly, and do not follow redirects into private address ranges.

8. Data protection

Security controls should be proportionate to what the data is, which means classifying it first. A simple three-tier scheme — public, internal, sensitive — applied consistently is worth more than an elaborate one applied inconsistently.

  • Collect less. Data you do not hold cannot leak. Question every field that is stored "in case it is useful", particularly identifiers and free-text fields where people paste anything.
  • Encrypt in transit and at rest, which is now largely a configuration default rather than an engineering effort. The remaining decision is who controls the keys, which is driven by data classification and regulation.
  • Separate what you can. Keeping the most sensitive fields in a separate store with tighter access limits the blast radius of a compromise elsewhere.
  • Mask in non-production. Copying production data into test environments is common and is a frequent source of exposure, because test environments have weaker controls and broader access.
  • Delete on schedule. Retention policies that are enforced automatically, not aspirational. Old data is liability without value.
  • Watch the exits. Bulk export, reporting endpoints and support tooling are how data actually leaves in volume. Rate-limit, log and alert on unusual volumes.

9. Infrastructure and configuration

In cloud environments, misconfiguration has overtaken software vulnerabilities as the leading cause of exposure. The controls that matter are preventive rather than detective.

Default deny. Network access, permissions and public exposure should all start closed and open deliberately. The alternative — starting open and closing what you notice — guarantees that something stays open.

Policy as prevention. A rule that blocks creation of a publicly readable storage bucket is worth more than an alert reporting that one was created yesterday. Push controls as early as possible: policy checks on infrastructure code in the pull request are cheaper than runtime detection, which is cheaper than an incident.

Immutable, rebuilt infrastructure. Servers that are replaced rather than patched in place cannot accumulate undocumented changes, and rebuilding from a known definition is the fastest recovery path after compromise.

Segment the network. Flat networks mean one compromised component reaches everything. Segmentation limits lateral movement, which is what turns an incident into a breach.

Least privilege for workloads. A service that only reads from one table should have permissions to read from one table. Over-permissioned service accounts are how a minor vulnerability becomes a major one.

10. Logging, monitoring and detection

Prevention fails eventually. The measure of a mature team is how quickly they notice, and the median time to detect a breach is still measured in weeks or months across the industry.

Log the events that describe security-relevant activity: authentication successes and failures, authorisation denials, privilege changes, access to sensitive records, configuration changes, and administrative actions. Each entry needs who, what, when, from where, and a correlation identifier tying it to a request.

Critically, never log secrets, tokens, card numbers or full personal records. Logs are widely accessible, retained for a long time and frequently shipped to third parties — they are one of the most common places sensitive data ends up by accident.

Alert on the small number of things that reliably indicate a problem: authentication failures spiking for one account, successful login from an unusual location immediately after failures, privilege escalation outside a change window, bulk data access far above normal, and any change to logging configuration itself. Ten alerts that always mean something beat a hundred that mostly do not, because the latter trains people to ignore them.

11. Security in the delivery pipeline

StageCheckGate or warn
Pre-commitSecret scanningGate — never let one in
Pull requestStatic analysis, dependency scan, infrastructure policyGate on new high-severity findings
BuildBill of materials, image scan, artefact signingGate on critical vulnerabilities
Pre-deployConfiguration policy, permission diffGate on policy violation
Post-deployDynamic scanning, exposure checksWarn, with an owner
ContinuousNew vulnerabilities in deployed versionsAlert with a service-level target

Two principles keep this from becoming an obstacle that teams route around. Gate on new findings, not on the existing backlog — blocking every build because of pre-existing issues stops delivery and produces pressure to disable the check entirely. And keep false positives low, aggressively tuning or removing noisy rules; a tool that cries wolf is worse than no tool, because it consumes attention that real findings need.

The most valuable pipeline control is also the simplest: nobody deploys to production by hand, and every change arrives through a reviewed, logged, reproducible path. That single property eliminates a large share of both accidental and deliberate misconfiguration.

12. Incident response without a security team

Most teams will handle their first serious incident without a dedicated security function. Preparation is what determines whether that goes well.

Write the plan before you need it, and keep it short: who to call, how to reach them out of hours, who can authorise taking a system offline, who talks to customers, and where you will coordinate if your normal tools are compromised. A plan nobody can find during an incident does not exist.

The response sequence. Contain first — isolate affected systems, revoke credentials, block the access path — while preserving evidence rather than wiping and rebuilding immediately, because you will need to know what happened. Then establish scope: what was accessed, over what period, by whom. Then eradicate and recover, from known-good definitions rather than by patching the compromised system in place. Then notify, according to your obligations, which in many jurisdictions have tight deadlines measured in hours.

Practise it. A tabletop exercise — walking through a plausible scenario for ninety minutes — reliably reveals that nobody knows who can authorise downtime, that the on-call rota does not include anyone who can revoke access, or that the backups have never been restore-tested. Finding that out during a rehearsal is considerably cheaper than during an incident.

Conduct a blameless review afterwards. The objective is to find the conditions that allowed the incident, not the person who made the mistake. Teams that punish individuals stop reporting problems, which is the single worst outcome available.

13. Twelve recurring mistakes

  1. Authorisation checked in the interface rather than the server. Hiding a button stops nothing.
  2. Long-lived API keys. The credential most likely to leak and least likely to be rotated.
  3. Secrets in the repository. Permanent, distributed, and found by automated scanners quickly.
  4. Dependency updates batched quarterly. Large risky upgrades that get deferred further.
  5. Test environments with production data. Weaker controls, broader access, same sensitivity.
  6. Flat internal networks. One compromise reaches everything.
  7. Over-permissioned service accounts. Turns a minor bug into a major incident.
  8. Sensitive data in logs. Widely readable, long retained, often shipped to third parties.
  9. Detective controls where preventive ones are available. An alert after the bucket went public is not a control.
  10. Alert fatigue. Hundreds of low-quality alerts train everyone to ignore the real one.
  11. No tested restore. Backups that have never been restored are an assumption, not a control.
  12. Security as a gate at the end. Findings arrive when changing anything is most expensive.

14. A security baseline you can implement in a quarter

Comprehensive security programmes are easy to describe and hard to start. What follows is a sequence a small team can genuinely complete in three months, ordered so that each step reduces real risk rather than producing documentation.

Weeks one and two — close the credential paths. Enable multi-factor authentication on every account with access to source, cloud infrastructure, production data or the build pipeline, with no exceptions for senior staff. Inventory every long-lived API key and access token in use, and record who owns each. This inventory is usually longer than anyone expects and frequently includes credentials belonging to people who left. Revoke what is unused, and schedule replacement of the rest.

Weeks three and four — stop secrets leaking. Turn on secret scanning across all repositories, including full history. Expect findings; historical repositories almost always contain something. Rotate anything found, and add a pre-commit check so the next one never lands. This is a two-day task that closes one of the most common breach paths permanently.

Weeks five and six — get dependency updates flowing. Enable automated dependency update pull requests, gated on your existing tests. Start with patch and minor versions only, so the change is low-risk and the team builds confidence in the automation. Generate a bill of materials during the build, so that when the next widely publicised vulnerability appears you can answer "are we affected" in minutes.

Weeks seven and eight — verify the recovery path. Restore a backup into a clean environment and confirm the system actually works, including the data. This is the control most commonly assumed and least commonly tested, and the failure rate on first attempt is high. Document the time it took, because that number is your real recovery objective regardless of what any policy claims.

Weeks nine and ten — run a threat model and write the authorisation tests. One session per significant service, following the four questions described earlier. Turn the findings into tickets. Separately, write automated tests that attempt to access another tenant's or another user's data through every significant endpoint. That test suite is often the highest-value security artefact a multi-tenant application has, and it costs a few days.

Weeks eleven and twelve — prepare for the incident. Write a one-page response plan: who to call, who authorises taking systems offline, who talks to customers, where you coordinate if normal tools are unavailable, and what your notification obligations are. Then run a ninety-minute tabletop exercise against a plausible scenario. The exercise will find gaps in the plan, which is exactly what it is for.

Twelve weeks, no dedicated security hire, no new platform. What it produces is not a mature security programme — it is the base that every mature programme is built on, and its absence is what turns ordinary mistakes into incidents.

15. Frequently asked questions

Where should a small team start?

Four things, in this order: multi-factor authentication on everything, automated secret scanning, automated dependency updates, and tested backups with a verified restore. Those four address the largest share of realistic compromise for a modest amount of work, and none of them require security expertise to implement. Everything else builds on that base.

Do we need a penetration test?

Eventually, and it is most valuable after you have fixed the obvious things — otherwise you pay specialist rates for findings a scanner would have given you. An annual test with a well-defined scope is a reasonable baseline for most products, more frequently for systems handling payments or health data. Treat the report as a sample of your weaknesses rather than an exhaustive list; a clean report means the tester did not find anything in the time available.

How do we handle vulnerabilities in dependencies we cannot update?

Assess reachability first — many reported vulnerabilities are in code paths you never execute, and documenting that analysis is a legitimate response. Where the risk is real and the update is blocked, compensating controls apply: restrict what the component can reach, add input validation in front of it, monitor for exploitation attempts, and set a date to revisit. Document the decision with an owner, because undocumented accepted risk becomes forgotten risk.

Should developers or a security team own security?

Developers own the security of what they build; a security function provides expertise, tooling and assurance. The model that fails is security as a gatekeeper at the end of delivery, because it creates an adversarial relationship and delivers findings when they are most expensive to fix. The model that works provides paved paths — libraries, templates and pipeline checks that make the secure approach the easy one.

How much logging is enough?

Enough to answer, after an incident, who accessed what and when, over your full retention period. That usually means authentication events, authorisation denials, administrative actions and access to sensitive records, retained for months rather than days. Balance against cost and against the risk of logs themselves becoming a data exposure — which is a real trade-off, not a theoretical one.

Is a bug bounty programme worth it?

Only once you can respond to findings quickly. A programme that generates reports nobody triages damages your reputation with the researcher community and creates a record of known-unfixed issues. Start with a published security contact and a clear disclosure policy — that costs nothing, and researchers who find something will have somewhere to send it rather than somewhere to publish it.

What about AI features and security?

Treat model inputs and outputs as untrusted. Content retrieved from documents or supplied by users may contain instructions aimed at your system, so never grant a model authority you would not grant an anonymous user, and enforce every permission in code that runs outside the model. Also treat what you send to a third-party model as a data transfer subject to your normal classification rules — because it is one.

How do we justify security work against feature work?

Frame it in terms of the specific incident it prevents and what that incident would cost — including notification obligations, customer churn and the engineering time an incident consumes. Vague appeals to good practice lose to concrete feature requests. It also helps to make security work small and continuous rather than a large project competing directly with the roadmap; most of what matters is a series of modest changes.

Key takeaways

  • The common attacks are mundane. Credentials, dependencies, exposed services, missing authorisation checks.
  • Threat model in an hour. Draw the boundaries, ask six questions, decide, assign owners.
  • Authorisation is the bug class scanners miss. Enforce centrally and test it explicitly.
  • Prevent rather than detect. Policy that blocks a mistake beats an alert that reports it.
  • Automate the dull work. Secret scanning, dependency updates and configuration checks in the pipeline.
  • Prepare for the incident. A short plan, a tested restore, and a rehearsal beat any amount of documentation.

Security is not a project with an end date; it is a property that decays unless maintained. The teams that stay safe are rarely the ones with the most sophisticated tooling — they are the ones where the boring things are automated, owned, and quietly checked every day.

Enjoyed this article?

Get more engineering insights from ELIVTECH — or talk to us about your project.

Get in touch