Hardening Identity and Access Management:
OWASP SPVS first line of defence
The first requirements subcategory of the OWASP SPVS Plan phase is V1.1 Identity and Access Management. Different projects require different levels of security, so the range of requirements in this subcategory is broad, from basic encryption to full management in centralized Identity Providers.
Let’s break down the requirements across all levels.
Level 1: Foundational
The absolute minimum. Every project requires encrypted secrets.
V1.1.8 Secrets encryption at rest and in transit
Secrets such as API keys, database credentials, and tokens must be encrypted both at rest and in transit.
Lack of encryption is like a key left in a lock. During secret transit, hackers can sniff your network traffic and read unencrypted packets, compromising your credentials. On the other hand, if a developer commits a secret to a public repository, anyone can gain access to it.
Leaking secrets triggers a devastating chain reaction, leading to massive financial losses, permanent reputational damage, and severe GDPR compliance violations.
Implementation Best Practices
At Rest: You cannot store plain-text secrets in an .env file in your repo. Instead, use service such as GitHub Secrets or Key Management Service (KMS) to store them encrypted and securely pass them to your app. For secrets related to your CI/CD workflows on GitHub, go to your repo settings ->
Secrets and variables->Actions->New repository secret. Then write the name of your secret (e.g., MY_SONAR_TOKEN) and the secret itself, then clickAdd secret. Now you can use it in your GitHub Actions workflow:jobs: sonarqube: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - name: SonarQube Scan uses: SonarSource/sonarqube-scan-action@v4.1.0 env: SONAR_TOKEN: ${{ secrets.MY_SONAR_TOKEN }}In Transit: Use TLS 1.2+ everywhere secrets are transmitted, between your computer to Key Management Service, from your backend app to destination external service such as OpenAI API keys. You cannot forget about internal traffic between your microservices; it also has to enforce TLS 1.2+. You can force the use of HTTPS on apps that are under your control by redirecting traffic from HTTP to HTTPS and setting the HSTS header. Here it's a simple Nginx configuration:
server { listen 80; listen [::]:80; server_name YOUR_HOST; return 301 https://$host$request_uri; } server { listen 443 ssl; listen [::]:443 ssl; server_name YOUR_HOST; ssl_certificate /path/to/certificate.pem; ssl_certificate_key /path/to/key.pem; ssl_protocols TLSv1.2 TLSv1.3; add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always; ... }
An unencrypted secret is not a secret; anyone can access it with simple tools. Plan to encrypt secrets at rest, use Key Management Service for storing them, and securely provide them to your app. Make sure that during transit, all network traffic is encrypted so a captured packet is unreadable.
Level 2: Standard
The goal of this level is to eliminate single points of failure. Even if a password is leaked or a service token is compromised, the system must have layers of defense to prevent a total breach.
V1.1.1 & V1.1.6 Multi-Factor Authentication (MFA) Everywhere
This requirement is about enforcing the usage of multiple-factor authentication in every possible place, from developer laptops to the Version Control System.
Most modern tools support Multi-Factor Authentication like OTP codes and physical security keys. You should prioritize phishing-resistant methods like FIDO2 physical security keys over standard OTP codes. The security key is a little device like a USB stick. After logging in with a password, you have to plug in and touch it to authenticate.
In a Level 2 system, you have multiple levels of defense, so a leaked password is not enough to compromise it.
Implementation Best Practices
VCS Enforcement: You have to enable MFA requirements at the Organization level to make sure anyone without MFA can have access to your source code. To set it on GitHub, go to Organization Settings -> Authentication security -> enable "Require two-factor authentication for everyone". Now everyone has to configure MFA first, then they can have access to the repo.
Developer Workstations: Enable MFA login on local accounts on all workstations. You can use, e.g., the Yubico Login Tool for local accounts or Windows Hello for Business for AD accounts. Yubico Login Tool configuration step by step.
Relying solely on passwords these days is unacceptable. If a GitHub account password leaks or a developer logs in on a phishing site looking like GitHub, an attacker gains immediate access to your source code. In the same scenario, but with enabled MFA, an attacker cannot do anything. The security key is not configured on the phishing domain, and a developer won't be able to authenticate on it.
V1.1.5 Least Privilege for Service Accounts
This requirement focuses on the Principle of Least Privilege specifically for service accounts and tokens used in VCS and CI/CD pipelines. You have to make sure that service accounts and tokens are granted only the minimum permissions required to perform their job.
Over-permissioned tokens are those, e.g., having reading, creating, and deleting repositories permission, while the read is only needed. In that situation, if a token is compromised, an attacker gains full control over your repositories. He can access your source code, then delete the repo.
Token with excessive permissions = excessive compromise when leaked.
Implementation Best Practices
VCS Scoped Permissions: Avoid using Personal Access Tokens (PAT) with excessive permissions. Instead, use GitHub Apps or Fine-grained PATs. They allow granting specific permissions to repositories and actions, e.g., read-only access to contents, with no ability to change settings.
Pipeline Token Permissions: In GitHub Actions, always define the permissions in your YAML file. By default, the GITHUB_TOKEN may have permissions that are unnecessary. Restrict it to the absolute minimum required for the job:
jobs: deploy: runs-on: ubuntu-latest permissions: contents: read packages: write steps: - uses: actions/checkout@v4 - name: Deploy Package run: npm publish
V1.1.7 Quarterly audit on VCS Administrators
The admins of Version Control System can disable branch protection, delete audit logs, invite malicious external users, and more. This opens up a large field for exploitation.
This requirement ensures that the VCS Administrator accounts are audited quarterly. It's necessary to ensure that the old admin accounts are removed and existing users don't have excessive admin permissions.
The forgotten old admin account can be compromised, and your entire security policy can be easily disabled.
Implementation Best Practices
The Rule of Least Privilege: You should have the least possible number of admin accounts. Everyone else should have standard user accounts with adjusted permissions.
Automated Review Reminders: Set an admin account audit reminder every quarter, e.g., in a calendar or Jira ticket. During this audit, ask three questions for every admin:
- Who is the admin?
- What does the admin do?
- What permissions does the admin need?
Audit Logs Analysis: Use the VCS audit log to verify what administrators are actually doing. On GitHub, go to
Organization Settings->Compliance->Audit log. Now you can filter by operations e.g.,action:org.update_memberto check if admin granted excessive permissions to someone else,protected_branch.destroyto check if someone disabled branch protection rules.
Level 3: Advanced
The goal of the highest SPVS level is to have centralized control and proactive hygiene. At this scale, every manual management is a liability. You need a "Single Source of Truth" for every person and every script that can access and modify your code.
V1.1.2 Centralized Identity Provider (IdP)
When you have hundreds of employees and tens of tools, managing separate accounts for each of them without any mistakes is impossible. If an employee leaves, you have to manually delete their account on GitHub, Jira, AWS, and more. One missed account can be used as an exploit, and malicous activity is done.
Under this requirement, you have to control all identities in one place to have a single source of truth. In this way, you can disable an account with just one click and don't have to go on each platform separately. Depending on your infrastructure, you can choose from a variety of available centralized Identity Providers, like Microsoft Entra ID or Okta.
A centralized IdP allows for "One-Click Offboarding" instantly revoking access across all tools the moment an identity is deactivated.
Implementation Best Practices
Enforce SAML SSO: This enforcement means that users have to log in to a platform using your IdP. Any account not managed through your IdP cannot access it, so you have full control over all accounts. To configure it on a GitHub Organization, go step by step using the official GitHub docs. Before enforcing, ensure that all necessary accounts have been linked, or they will lose access to your organization after processing.
SCIM Provisioning: SCIM automates the adding and deleting of users. When a user is added to a group in your IdP, they automatically get access to GitHub. When they are removed, the account is deleted. Using Terraform, you can map your IdP groups to GitHub Teams using the following code:
resource "github_team" "xyz_devs" { name = "xyz-devs" description = "developers in XYZ project" privacy = "closed" } resource "github_team_sync_group_mapping" "xyz_devs_mapping" { team_slug = github_team.xyz_devs.slug group { group_id = "your IdP group id" group_name = "XYZ-Devs" group_description = "Developers in XYZ project" } }
V1.1.3 End-to-End The Least Privilege in the Pipeline
While Level 2 focused on service tokens, Level 3 requires that every single tool in your pipeline follows the Principle of Least Privilege. This includes your build servers, security scanners, deployment scripts, and more.
In a complex pipeline, tools have been granted permissions to certain actions, or they can even communicate with each other to get a result from a previous step of the pipeline. In that condition, if the SAST tool is overpermissioned, which should have read-only access to code and upload a report permission, a vulnerability in it could lead to a compromise of your entire infrastructure.
Implementation Best Practices
OIDC for Cloud Providers: Stop using long-lived secret keys like AWS Access Keys in your CI/CD. Instead, use OpenID Connect (OIDC). This allows your GitHub Action to request a short-lived, scoped token directly from the cloud provider. The OIDC eliminates "secret sprawl", so you don't store sensitive, long-term credentials in your CI/CD configuration or logs.
Configuring OpenID Connect in Amazon Web Services
Environment Protection Rules: Use GitHub Environments to require manual approvals before sensitive actions, especially during production deployments. Without approval, a script cannot be executed.
How to configure it on GitHub? In the beginning, configure the environment following GitHub Docs: Managing environments for deployment. During that process, you set the environment and people with deployment approval permissions. After this process in your deployment job, add the environment:
jobs: deploy: environment: production runs-on: ubuntu-latest steps: - run: ./deploy.sh
V1.1.4 Automated Removal of Inactive Identities
This requirement ensures accounts that haven't been used for a certain period, e.g., 30 days, are automatically disabled or removed.
Attackers seek to remain undetected, so they look for unmonitored areas of the application, such as inactive, forgotten accounts. As the account is unmonitored, the breach also could go unnoticed for months; the user left the company, so there is no one to report suspicious activity.
An inactive account is an unmonitored risk. If it's not being used, it should be disabled or deleted.
Implementation Best Practices
IdP Inactivity Policies: Use automated inactive user disable scripts instead of manual work, where you can omit account or forget to check if there is an inactive user. For Microsoft Entra ID, you can set a policy that automatically disables any user who hasn't signed in for, e.g., 30 days.
How to implement (Entra ID): Go to
Identity Governance->Lifecycle workflows->workflows. Create a new workflow template and set the Sign-in inactivity trigger. Next, set the desired days of inactivity until disabled and choose the scope. ClickReview + Create, and it's done. Detailed instruction is available in official Microsoft docs: Microsoft: Manage inactive users using Lifecycle Workflows.Token Expiration Policies: Everything that I described before is also related to Personal Access Tokens (PATs). You also have to check for stale PATs or enforce a maximum lifespan for all tokens.
For GitHub Organization, go to
Personal access tokens->Settingsand selectRestrict the maximum allowed lifetime of personal access tokens. Set the desired token expiry, e.g, 30 days. From this moment, you have to rotate keys regularly, and there is no chance to have forgotten token for more than 30 days.
Checklist for OWASP SPVS V1.1 Identity and Access Management
Level 1 (Foundational)
- V1.1.8 Secrets encrypted at rest (Key Management Service) and in transit (HTTPS/TLS)
Level 2 (Standard)
- V1.1.1 MFA is enabled on developer laptops
- V1.1.5 Service account tokens reviewed for over-permissioning
- V1.1.6 Version Control System requires MFA for all users
- V1.1.7 Scheduled quarterly audit for Version Control System administrator accounts
Level 3 (Advanced)
- V1.1.2 All identities are managed through a centralized Identity Provider
- V1.1.3 Pipeline tools follow the principle of least privilege
- V1.1.4 Inactive accounts and tokens are automatically disabled after a certain period of inactivity
What’s Next?
Now we've locked down who has access. In the next post, we’ll go through the next subcategory of OWASP SPVS: V1.2: Hardening User Machines, where we'll protect devices from which a project's source code can be accessed. We'll examine ways to protect devices from physical loss and unauthorized remote access.
