Home
Docs
GitHub
Blog
Categories

Overview of Security Principles

The field of software security is ever-evolving, with threats and mitigation strategies constantly changing. Some principles, however, remain foundational to securing a software environment. Here are some of the key security principles and concepts you need to know.

Suggested read: Professor Z. Berkay Celik's excellent paper on security design principles.

Confidentiality

Confidentiality refers to the principle of ensuring restricted access to information only to legitimate users or components. In the context of software security, this translates to data encryption, user authentication, imposition of access controls, etc.

Consider the example of user authentication in Node.js:

const express = require('express');
const bcrypt = require('bcrypt');

const app = express();

app.post('/users', async (req, res) => {
    try {
        const hashedPassword = await bcrypt.hash(req.body.password, 10)
        const user = { name: req.body.name, password: hashedPassword }
        users.push(user)
        res.status(201).send()
    } catch {
        res.status(500).send()
    }
})

The above code uses bcrypt module to hash passwords, thereby maintaining confidentiality.

Integrity

Integrity ensures that data or resources are not altered in unauthorized ways. This can be reflected in software through checksums, digital signatures, and control systems like version control.

For example, a simple hash function can be constructed for ensuring data integrity:

const crypto = require('crypto');

function getHash(data) {
    const hash = crypto.createHash('sha256');
    hash.update(data);
    return hash.digest('hex');
}

let data = 'Hello, World!';
let hash = getHash(data);

console.log(`Hash of '${data}' is '${hash}'`);

The resulting hash is a representation of the original data. Any alteration of the original data will result in a different hash.

Defense in Depth

Defense in Depth is the layered security approach to protect against security breaches. This might include firewalls, intrusion detection systems, and multiple unique and diverse security measures designed to protect against different types of threats.

For instance, in web applications, security layers might include HTTPS encryption, server-side validation and sanitization, database prepared statements, password hashing, etc.

Principle of Least privilege

The Principle of Least Privilege (POLP) entails minimizing the permissions for a process or user to the bare minimum required for the task at hand.

In a Node.js application, be mindful about the permissions your application has in its runtime environment. You may limit it by running Node.js in a Docker container with dropped capabilities, or running the Node.js process as a non-root user.

Secure Defaults

Secure Defaults means having the default configurations of the system as secure as possible. This reduces the likelihood of a security breach due to misconfiguration.

For instance, in Express.js, Helmet.js can be used to set some HTTP headers following strong security practices:

const express = require('express');
const helmet = require('helmet');

const app = express();

app.use(helmet());

Here, the helmet() middleware function adds several HTTP headers to make your application more secure.

Fail Securely

Software should handle security failures gracefully, avoiding exposure of sensitive information or providing access to unauthorized users during such events. This concept is referred to as "fail securely."

Consider the login function in an Express.js application:

app.post('/login', (req, res) => {
  authenticate(req.body.username, req.body.password, (error, user) => {
    if (error || !user) {
      res.status(401).json({ error: 'Invalid username or password' });
    } else {
      req.session.userId = user._id;
      res.json({ status: 'Login successful' });
    }
  });
});

We utilize the authenticate function to validate user credentials. If verification fails, the system denies the login attempt and provides a generic error message, giving no indication of whether the username exists or the password is incorrect.

Isolation

In a software system, isolation refers to running independent tasks in such a way that failure or compromise of one won't affect the others. This can be achieved through various strategies, such as process isolation, virtualization, and containerization.

For example, running Node.js apps in Docker containers:

FROM node:14
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 8080
CMD [ "node", "server.js" ]

Each container runs an instance of your app, isolated from others. If one instance crashes or gets compromised, it won’t affect the others.

Don't Trust Services

Software should always validate data from other services or systems. Never trust services implicitly—think "trust but verify."

Express.js middleware can illustrate this principle:

app.use(express.json()); // Parse JSON body
app.use((req, res, next) => {
  if(isDataValid(req.body)) next(); // Verify incoming data
  else res.status(400).send('Invalid data');
});

The middleware function isDataValid checks the validity of the incoming request data. If not valid, it immediately sends an error response.

Economy of Mechanism

Simple designs are easier to secure. The principle of the economy of mechanism suggests that software should be as simple as possible, yet effective.

An example could be a concise Express.js route handler:

app.get('/users', async (req, res) => {
  try {
    const users = await User.find();
    res.json(users);
  } catch (e) {
    res.status(500).send('Server error');
  }
});

The route handler is simple, clear, and straightforward, making it easier to identify and prevent security vulnerabilities, hence adhering to the Economy of Mechanism.

Security Through Obscurity is not Enough

Obscuring the code and design decisions may add a level of difficulty for attackers, but it's not a standalone security fix. Security should be guaranteed even when the system or code is understood by an attacker. Code should be auditable and, if possible, open source.

Least Common Mechanisms

When possible, resources should not be shared among users. Shared resources can bring security concerns. If unavoidable, proper access control measures should be put in place.

Psychological Acceptability

Security mechanisms shouldn't make the system more difficult to use. If a security mechanism is too complicated, users may try to bypass it or use it incorrectly — resulting in decreased security.

Principle of Complete Mediation

The Principle of Complete Mediation requires every access to every resource to be checked for authority. Caching credentials or permissions for efficiency could lead to unauthorized access if permissions change.

Consider a token-based authentication system in Node.js where the JWT token is validated for each request:

const jwt = require("jsonwebtoken");

app.use(async (req, res, next) => {
  if (req.headers['authorization']) {
    const token = req.headers['authorization'].split(" ")[1];
    jwt.verify(token, process.env.ACCESS_TOKEN_SECRET, (err, user) => {
      if (user) {
        req.user = user;
        next();
      } else {
        res.status(403).send('Unauthorized')
      }
    });
  } else {
    res.status(403).send('Unauthorized')
  }
});

Separation of Duties

Separation of duties (SoD) is a concept where more than one person required to complete a task. It acts as an internal control intended to prevent fraud and error. For example, in a financial web application, one user may be allowed to create a payee, but a different user should be required to authorize the payment.

Best Practices

Finally, there are certain industry-adopted best practices that have stood the test of time.

  • Regularly Update and Patch: Keep your software and its dependencies up to date. Make sure to apply patches promptly.
  • Use Strong Authentication: Always use strong authentication mechanisms. When possible, implement multi-factor authentication.
  • Encrypt sensitive data: Whenever sensitive data is stored or communicated, it should be encrypted to prevent unauthorized access.
  • Use Security Headers: When developing web applications, remember to use security headers like Content-Security-Policy, X-Frame-Options, and Strict-Transport-Security.
  • Regular Security Audits: Regularly conduct security audits to uncover and address vulnerabilities. This includes code reviews, automated testing, and penetration tests.

Software security is an evolving field, and as a developer or security specialist, it’s important to stay updated on the latest trends and threats. The principles and concepts outlined in this guide provide a strong foundation for developing secure software applications.