DevSecOps

Code Repository Security Hardening

Your source code repository is the starting point of your entire supply chain. Hardening it against unauthorized access, code injection, and configuration tampering is non-negotiable.

Shadab Khan
Security Architect
6 min read

Every supply chain attack has a starting point, and increasingly, that starting point is the code repository. Whether it's an unauthorized commit, a compromised CI/CD configuration, or a malicious pull request, repository security failures cascade into every downstream artifact, deployment, and user.

Hardening your repository isn't just about access control. It's about ensuring the integrity of every commit, every configuration change, and every build trigger from source to deployment.

The Repository Attack Surface

Direct Code Injection

Attackers with write access to a repository can inject malicious code directly. This might come from:

  • Compromised developer credentials.
  • Insider threats.
  • Stolen SSH keys or personal access tokens.
  • Compromised CI/CD service accounts.

Pull Request Poisoning

Even without write access, attackers can submit pull requests that:

  • Introduce subtle backdoors hidden in large diffs.
  • Modify CI/CD configurations to exfiltrate secrets.
  • Add malicious dependencies.
  • Change build scripts to inject payloads.

CI/CD Configuration Manipulation

Repository-stored CI/CD configurations (.github/workflows/, .gitlab-ci.yml, Jenkinsfile) are high-value targets. Modifying these files can:

  • Run arbitrary code in the CI/CD environment.
  • Exfiltrate secrets available to the pipeline.
  • Modify build outputs without changing application code.
  • Push compromised artifacts to registries.

Dependency File Manipulation

Changes to package.json, requirements.txt, go.mod, or similar dependency files can introduce malicious packages without modifying any application code.

Git History Rewriting

Without proper protections, attackers can rewrite git history to:

  • Remove evidence of malicious commits.
  • Replace legitimate commits with malicious ones.
  • Alter blame information to obscure the source of changes.

Hardening Measures

Access Control

Principle of least privilege: Grant the minimum access needed for each role.

  • Read access for most team members.
  • Write access only for active contributors.
  • Admin access only for repository administrators.
  • Separate service accounts for CI/CD with scoped permissions.

Review personal access tokens: PATs are often over-permissioned and never rotated. Audit existing tokens, reduce their permissions, and set expiration dates.

SSH key management: Require SSH key rotation. Monitor for key sharing across accounts. Consider requiring SSH certificate authentication for larger organizations.

Remove inactive accounts: Regularly audit repository access and remove accounts that are no longer active.

Branch Protection

Branch protection rules are your last line of defense against unauthorized changes:

Require pull request reviews: All changes to protected branches must go through a pull request with at least one (ideally two) approving reviews.

Require status checks: Mandate that CI/CD checks pass before merging. This ensures tests, linting, and security scans are run.

Require signed commits: Enforce GPG or SSH commit signing on protected branches. This ensures every commit is attributable to a verified identity.

Restrict force pushes: Prevent force pushes to protected branches. History rewriting should never be possible on main branches.

Require linear history: Prevents merge commits and ensures a clean, auditable history.

Restrict who can dismiss reviews: Don't allow the PR author to dismiss review requirements.

Include administrators: Branch protection should apply to administrators too. No exceptions.

Commit Signing

Commit signing provides cryptographic proof of commit authorship:

GPG signing: The traditional approach. Developers sign commits with their GPG key, and the repository verifies the signature against their public key.

SSH signing: A newer, simpler approach supported by GitHub and GitLab. Developers sign commits with their SSH key.

Gitsign (Sigstore): Keyless commit signing using Sigstore's Fulcio CA. No key management required -- signing is tied to the developer's OIDC identity.

Enforce signature verification on protected branches so unsigned or incorrectly signed commits are rejected.

CI/CD Configuration Security

CODEOWNERS: Use CODEOWNERS files to require specific reviewers for CI/CD configuration changes. Security team members should be required reviewers for pipeline modifications.

Pin action versions: Never use @latest or @main for CI/CD actions. Pin to specific commit SHAs:

# Bad
uses: actions/checkout@main

# Good
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.3

Separate CI/CD credentials: Don't use the same credentials for building and deploying. Build environments should have read access; deployment environments should have write access to specific targets only.

Immutable pipeline definitions: Where possible, use pipeline definitions that can't be modified by the code being built.

Dependency Management

Lock files: Always commit lock files. They pin exact versions and include integrity hashes.

Automated dependency updates: Use Dependabot, Renovate, or similar tools to keep dependencies current. Review updates before merging.

Dependency review: Require review for any change that adds, removes, or modifies dependencies. Tools like GitHub's dependency review action can flag risky changes.

Private registries: Proxy public registries through a private registry with an allowlist to prevent dependency confusion.

Audit Logging

Enable comprehensive audit logs: Track who did what, when, and from where. This includes:

  • Repository access events.
  • Configuration changes.
  • Branch protection modifications.
  • Webhook additions.
  • Deploy key creation.
  • Team membership changes.

Monitor audit logs: Don't just collect them -- actively monitor for suspicious activity. Alert on:

  • Access from unusual locations.
  • Configuration changes outside of change windows.
  • New deploy keys or webhooks.
  • Permission escalations.

Retain logs: Keep audit logs for at least a year. In the event of a supply chain compromise, you'll need historical data to determine the scope.

Webhook and Integration Security

Audit webhooks regularly: Webhooks can send repository data to external endpoints. Unauthorized webhooks can exfiltrate code or trigger malicious pipelines.

Verify webhook signatures: All webhook endpoints should verify the webhook signature to prevent forgery.

Limit integration permissions: Third-party integrations (CI/CD, monitoring, etc.) should have the minimum permissions necessary.

Secret Scanning

Enable secret scanning: GitHub, GitLab, and other platforms offer secret scanning that detects accidentally committed credentials. Enable it and act on alerts immediately.

Pre-commit hooks: Use tools like git-secrets or detect-secrets as pre-commit hooks to prevent secrets from being committed in the first place.

Historical scanning: Scan the entire repository history for secrets, not just new commits. Secrets committed years ago and still in history are still exploitable.

Organizational Practices

Security Champions

Designate security champions within each development team who:

  • Review security-sensitive changes.
  • Maintain awareness of supply chain threats.
  • Enforce repository security policies.
  • Serve as a bridge between security and development teams.

Regular Access Reviews

Conduct quarterly access reviews:

  • Who has access to each repository?
  • Are access levels appropriate for current roles?
  • Are there stale accounts or tokens?
  • Are service accounts properly scoped?

Incident Response

Have a plan for repository compromise:

  • How to rapidly revoke compromised credentials.
  • How to identify what was changed during a compromise.
  • How to verify the integrity of code after an incident.
  • How to notify downstream consumers.

How Safeguard.sh Helps

Safeguard.sh extends repository security by providing comprehensive analysis of what's actually in your software, regardless of how it got there. When code leaves your repository and enters the build pipeline, Safeguard.sh generates SBOMs that capture every component, enabling you to verify that the built artifact matches the intended source. If unauthorized changes slip past repository controls, Safeguard.sh's integrity verification and vulnerability scanning catch the discrepancy at the artifact level. Policy gates enforce your security standards on every build output, ensuring that compromised code doesn't reach production even if it makes it into the repository.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.