Back to Checks
aws

CodeCommit repository has no secrets in its default branch

codecommit_repository_no_secrets
Service: codecommitSeverity: high
OfficialBuilt-inVerified
Categories:secretsci-cd
Resource Type:AwsCodeCommitRepository

AWS CodeCommit repositories are scanned for hardcoded secrets such as API keys, tokens, and passwords in every file tracked at the tip of the default branch. CodeCommit repositories, including their branches and commit history, are a top source of leaked credentials, and secrets can persist in history even after being removed from HEAD.

Risk

Hardcoded secrets committed to a CodeCommit repository can be read by any principal with codecommit:GetFile or codecommit:GetBlob permission, and remain in the commit history even after being deleted from the tip of a branch. Exposed credentials enable unauthorized access to downstream systems and services, leading to data exfiltration and further compromise.

Run this check with Prowler

Run in Prowler Cloud
Run
prowler aws --checks codecommit_repository_no_secrets

Recommendation

Remove hardcoded credentials from all files committed to CodeCommit repositories. Store secrets in AWS Secrets Manager or AWS Systems Manager Parameter Store and reference them at runtime instead of committing them. Rotate any credential that has been committed, even if it is later removed, since it remains recoverable from commit history. Apply least-privilege IAM policies to repository access and consider using pre-commit hooks or AWS CodeGuru Secrets Detector to catch secrets before they are pushed.

Remediation

CLI

aws codecommit get-file --repository-name <repository-name> --file-path <file-path>
git filter-repo --path <file-path> --invert-paths

Other

1Identify every file and commit that contains the hardcoded secret using `git log -p` or the file and line reported by this check.
2Revoke and rotate the exposed credential immediately, since it may already be present in the commit history.
3Remove the secret from the current file content and replace it with a reference to AWS Secrets Manager or AWS Systems Manager Parameter Store.
4If the secret must be purged from history, rewrite the repository history (for example with `git filter-repo` or the BFG Repo-Cleaner) and force-push the cleaned branches.
5Enable a pre-commit or pre-receive hook (such as `detect-secrets` or AWS CodeGuru Secrets Detector) to prevent future commits containing secrets.

Check Code

import fnmatch
from collections import defaultdict
from typing import List

from prowler.lib.check.models import Check, Check_Report_AWS
from prowler.lib.utils.utils import (
    SecretsScanError,
    annotate_verified_secrets,
    detect_secrets_scan_batch,
)
from prowler.providers.aws.services.codecommit.codecommit_client import (
    codecommit_client,
)


class codecommit_repository_no_secrets(Check):
    """Ensure CodeCommit repositories do not contain hardcoded secrets.

    Scans every file tracked at the tip of each repository's default branch
    for embedded credentials such as API keys, tokens, or passwords. Only the
    default branch at its current commit is scanned by default; full commit
    history is not walked, since secrets that persist in history but have
    been removed from the tip of the branch are out of scope for this check.

    - PASS: No secrets are detected in the files of the default branch.
    - FAIL: A secret is detected in one or more files of the default branch.
    - MANUAL: The secret scan could not be completed — either the scanner
      failed or some file content could not be retrieved (for example, files
      larger than 6 MB that the CodeCommit API refuses to return) — and
      manual review is required.
    """

    def execute(self) -> List[Check_Report_AWS]:
        """Execute the CodeCommit repository secrets check.

        Iterates over all discovered repositories, scans every file at the
        tip of the default branch for secrets in batched invocations, and
        reports any findings, including the repository, branch, commit, and
        file where the secret was found (never the secret value itself).

        Returns:
            List[Check_Report_AWS]: A list of report objects with check results.
        """
        findings = []
        secrets_ignore_patterns = codecommit_client.audit_config.get(
            "secrets_ignore_patterns", []
        )
        # Glob patterns of file paths inside the repository to skip when
        # scanning for secrets (e.g. "*.deps.json" or "package-lock.json").
        secrets_ignore_files = (
            codecommit_client.audit_config.get("secrets_ignore_files", []) or []
        )
        validate = codecommit_client.audit_config.get("secrets_validate", False)
        repositories = (
            list(codecommit_client.repositories.values())
            if codecommit_client.repositories
            else []
        )

        # Collect every file tracked at the tip of each repository's default
        # branch and scan them in batched invocations instead of one
        # subprocess per file. Findings are keyed by (repository index, file
        # path) so they can be grouped back per repository. Files whose
        # content could not be retrieved are recorded so the repository is
        # reported as MANUAL instead of a false PASS.
        unscanned_files_by_repository = defaultdict(list)

        def payloads():
            for repo_index, repository in enumerate(repositories):
                if (
                    not repository.default_branch
                    or not repository.default_branch_commit_id
                ):
                    continue
                for (
                    file_path,
                    file_content,
                ) in codecommit_client.get_repository_files_content(repository):
                    if any(
                        fnmatch.fnmatch(file_path.lstrip("/"), pattern)
                        for pattern in secrets_ignore_files
                    ):
                        continue
                    if file_content is None:
                        unscanned_files_by_repository[repo_index].append(file_path)
                        continue
                    if not file_content:
                        continue
                    yield (repo_index, file_path), file_content.decode("latin-1")

        scan_error = None
        try:
            batch_results = detect_secrets_scan_batch(
                payloads(), excluded_secrets=secrets_ignore_patterns, validate=validate
            )
        except SecretsScanError as error:
            batch_results = {}
            scan_error = error

        findings_by_repository = defaultdict(dict)
        for (repo_index, file_path), file_findings in batch_results.items():
            findings_by_repository[repo_index][file_path] = file_findings

        for repo_index, repository in enumerate(repositories):
            report = Check_Report_AWS(metadata=self.metadata(), resource=repository)
            report.status = "PASS"
            report.status_extended = f"CodeCommit repository {repository.name} does not have secrets in its default branch."

            has_default_branch = bool(
                repository.default_branch and repository.default_branch_commit_id
            )

            if not has_default_branch:
                report.status_extended = (
                    f"CodeCommit repository {repository.name} has no default "
                    f"branch, so there is no content to scan for secrets."
                )
                findings.append(report)
                continue

            if scan_error:
                report.status = "MANUAL"
                report.status_extended = (
                    f"Could not scan CodeCommit repository {repository.name} "
                    f"default branch for secrets: {scan_error}; manual review is required."
                )
                findings.append(report)
                continue

            files_with_secrets = findings_by_repository.get(repo_index)
            unscanned_files = unscanned_files_by_repository.get(repo_index)
            if files_with_secrets:
                all_secrets = []
                secrets_found = []
                for file_path, file_findings in files_with_secrets.items():
                    all_secrets.extend(file_findings)
                    secrets_found.extend(
                        f"{file_path} on line {secret['line_number']} ({secret['type']})"
                        for secret in file_findings
                    )

                secrets_string = ", ".join(secrets_found)
                report.status = "FAIL"
                report.status_extended = (
                    f"CodeCommit repository {repository.name} has "
                    f"{'secrets' if len(secrets_found) > 1 else 'a secret'} in branch "
                    f"{repository.default_branch} (commit {repository.default_branch_commit_id}) -> {secrets_string}."
                )
                annotate_verified_secrets(report, all_secrets)
            elif unscanned_files:
                report.status = "MANUAL"
                report.status_extended = (
                    f"Could not retrieve the content of "
                    f"{', '.join(unscanned_files)} in CodeCommit repository "
                    f"{repository.name} default branch (for example, files "
                    f"larger than 6 MB cannot be downloaded through the "
                    f"CodeCommit API), so they were not scanned for secrets; "
                    f"manual review is required."
                )

            findings.append(report)

        return findings