2026年5月13日 星期三

[Git] Gitleaks

Gitleaks

 gitleaks     git    security    sast  

https://github.com/gitleaks/gitleaks

Gitleaks is a SAST(Static Application Security Testing) tool for detecting hardcoded secrets like passwords, API keys, and tokens in Git repositories.


Installation

See https://github.com/gitleaks/gitleaks#installing

The quickest way is using Go.

git clone https://github.com/gitleaks/gitleaks.git \
cd gitleaks \
make build


After build, copy the gitleaks.exe to $GOBIN and check the version.

cp gitleaks.exe $GOBIN/
gitleaks version



Basic concepts

  • Detect mode: scan a repository and report potential secrets.
  • Exit code: by default, Gitleaks exits with a non‑zero code if leaks are found. This is useful for CI pipelines.

Commands

See full list at https://github.com/gitleaks/gitleaks#usage

Syntax:

gitleaks <COMMAND> [FLAGS]


Command Description
completion Generate the autocompletion script for the specified shell
dir scan directories or files for secrets
git scan git repositories for secrets
help Help about any command
stdin detect secrets from stdin
version display gitleaks version

Common flags:

Flag Description
--source PATH_OR_URL what to scan (local path or remote Git URL).
--report-path FILE where to write the JSON report.
--redact hide secret values in the output.
--no-git scan files as a regular directory (ignore Git history).

Gitleaks Configuration

Default configuration(rules and allowlist) is at gitleaks.toml. You can use your custom configuration by flag --config <CONFIG_PATH>. e.g.

gitleaks detect --source . --config ~/gitleaks.toml --redact



Audit a local repository

From the repository root

Change into your repo and run:

gitleaks detect --source . --redact


  • Scans the entire Git history of the repo
  • Prints findings to stdout
  • Redacts secret values in the output

Save a JSON report

gitleaks detect \
  --source . \
  --redact \
  --report-path gitleaks-report.json


You can then open gitleaks-report.json in any JSON viewer or feed it into other tools.

Scan only current files (no Git history)

The flag --no-git makes gitleaks only scans the current working tree.

gitleaks detect \
    --source . \
    --no-git \
    --redact \
    --report-path gitleaks-report.json



Audit a remote repository (e.g., GitHub)

We can scan a remote repository in two ways:

  1. Manually clone it, then scan locally (recommended for repeat scans)
    • You want to scan the same repo multiple times.
    • You want to commit the gitleaks.toml config into the repo.
  2. Let Gitleaks clone it on the fly (recommended for quick checks)

Scan Remote Repo by URL (no manual clone)

You can point --source directly at a Git URL.

Public Repository

gitleaks detect \
  --source https://github.com/OWNER/REPO.git \
  --redact
  --report-path gitleaks-OWNER-REPO-report.json


Gitleaks will:

  • Clone the repo into a temporary directory
  • Scan its history
  • Remove the temporary clone after completion

Private Repository

For private repositories you must provide credentials (for example, a GitHub Personal Access Token via environment variables or your Git credential helper).

e.g. Using a GitHub token in the URL:

gitleaks detect \
  --source https://GITHUB_TOKEN@github.com/OWNER/REPO.git \
  --redact


For long-term usage, consider the safer approaches:

  • Use environment variables and avoid hardcoding tokens in scripts.
  • Use the CI tools, like GitHub Action.

Exit codes and CI integration

Gitleaks returns different exit codes depending on the result of the scan

  • 0 = no leaks found
  • non-zero = leaks or errors

This behavior makes it easy to integrate into CI pipelines (GitHub Actions, GitLab CI, etc.):

  • Pass: No secrets detected → pipeline continues
  • Fail: Secrets detected → pipeline fails, forcing remediation

Typical CI command:

gitleaks detect --source . --redact --report-path gitleaks-report.json


2025年3月23日 星期日

[ASP.NET Core] Ocelot - Redis caching and custom Redis key

 Redis    Ocelot   API Gateway 





Introduction 


Ocelot is the API Gateway framework for ASP.NET Core, it supports memory caching and the cache key contains a hash value by default. We’ll implement the Redis caching and use our own cache key generating strategy.


The full sample code is at KarateJB/AspNetCore.Profiler.Sample.



Install Package



<PackageReference Include="StackExchange.Redis" Version="2.8.24" />

<PackageReference Include="Ocelot" Version="23.2.2" />

<PackageReference Include="Ocelot.Provider.Polly" Version="23.2.2" />


 



Implement



First we have to enable Ocelot in Program.cs.



using Ocelot.Cache;

using Ocelot.Configuration.File;

using Ocelot.DependencyInjection;

using Ocelot.Middleware;

using Ocelot.Provider.Polly;


var builder = WebApplication.CreateBuilder(args);


builder.Logging.ClearProviders();

// Add Ocelot configuration file

builder.Configuration.AddJsonFile("ocelot.json", optional: false, reloadOnChange: true);

builder.Services.AddOcelot(builder.Configuration).AddPolly();


builder.Services.AddControllers();


var app = builder.Build();


app.UseHttpsRedirection();

app.UseRouting();

app.UseAuthentication();

app.UseAuthorization();

await app.UseOcelot();

app.MapControllers();

app.Run();




Then complete the ocelot.json configuration, see Ocelot:Configuration.


Ocelot’s caching implementation and interfaces 


We can find the default caching implementation DefaultMemoryCache<T> in Ocelot source code; it implements the interface IOcelotCache<T>.


using Microsoft.Extensions.Caching.Memory;


namespace Ocelot.Cache;


public class DefaultMemoryCache<T> : IOcelotCache<T>

{

}



For the cache key generator, the implementation class is DefaultCacheKeyGenerator., which implements the interface ICacheKeyGenerator.


namespace Ocelot.Cache;


public class DefaultCacheKeyGenerator : ICacheKeyGenerator

{

}



Ocelot registers them by the following code in Ocelot source code “src\Ocelot\DependencyInjection\Features.cs”.


public static IServiceCollection AddOcelotCache(this IServiceCollection services) => services

        .AddSingleton<IOcelotCache<Regex>, DefaultMemoryCache<Regex>>()

        .AddSingleton<IOcelotCache<FileConfiguration>, DefaultMemoryCache<FileConfiguration>>()

        .AddSingleton<IOcelotCache<CachedResponse>, DefaultMemoryCache<CachedResponse>>()

        .AddSingleton<ICacheKeyGenerator, DefaultCacheKeyGenerator>()

        .AddSingleton<ICacheOptionsCreator, CacheOptionsCreator>();




In ASP.NET Core Dependency Injection, we can add multiple implementation classes for a single interface into the service containers. If we don’t specify which one to use, the last injected implementation class will be the default one. However, Ocelot checks if the service container already has the custom  implementation of the two interfaces, it won’t add its own implementations to the DI container to make sure that the our custom implementations are used. 


Thus, all we have to do is create our custom Redis caching and Redis key generator classes(services) and implement the interfaces, then register our services into the DI container.


 


Implement IOcelotCache<CachedResponse>


Let’s create a new class RedisCacheStore that implements IOcelotCache<CachedResponse>


 


namespace AspNetCore.Profiler.Gateway.Services

{

    public class RedisCacheStore : IOcelotCache<CachedResponse>

    {

        private readonly ILogger _logger;

        private readonly RedisSetting _redisSetting;

        private readonly IDatabase? _redisDb;

        private Func<string, string, string> RedisKey = (string region, string key) => $"{region}:{key}";


        public RedisCacheStore(ILogger<RedisCacheStore> logger, IOptions<AppSettings> options)

        {

            _redisSetting = options?.Value?.Redis ?? throw new ArgumentNullException(nameof(RedisSetting));


            // Initialize Redis connection

            var redis = ConnectionMultiplexer.Connect(_redisSetting.ConnectionString);

            _redisDb = redis.GetDatabase();

        }


        public void Add(string key, CachedResponse value, TimeSpan ttl, string region)

        {

            string redisKey = RedisKey(region, key);

            if (value is not null && _redisDb != null)

            {

                var cacheValue = JsonConvert.SerializeObject(value);

                _redisDb.StringSet(redisKey, cacheValue, expiry: ttl);

            }

        }


        public void AddAndDelete(string key, CachedResponse value, TimeSpan ttl, string region)

        {

            Add(key, value, ttl, region);

        }


        public CachedResponse Get(string key, string region)

        {

            string redisKey = RedisKey(region, key);

            RedisValue cachedData = _redisDb != null ? _redisDb.StringGet(redisKey) : RedisValue.Null;


            if (!cachedData.IsNullOrEmpty)

            {

                return JsonConvert.DeserializeObject<CachedResponse>(cachedData);

            }


            return null;

        }


        public bool TryGetValue(string key, string region, out CachedResponse value)

        {

            value = Get(key, region);

            return value != null;

        }


        public void ClearRegion(string region)

        {

            // Skip

        }

    }


}




The code has nothing special, but we have to know what “region and “key are, because they are related to the cache key management in Ocelot’s design. 


To enable caching for an upstreaming routine, we add the following configuration in Ocelot.json (see Ocelot document).


"FileCacheOptions": {

        "TtlSeconds": 3600,

        "Region": "payment",

        "EnableContentHashing": false

      }


The Region’s value is for the parameter “region”. A region is a concept of grouping of cache keys. For example, we can put cache keys “user:aaa” and “vip-bbb” in the same group: “users”, and “users” is the region. When we want to clear all the caches in the same group, the “region” is specified to clear all the cache keys that were put into this region. I suggest reading Ocelot’s memory cache implementation DefaultMemoryCache<T> and you will find out how Ocelot manages the cache keys with “region”.


In Redis, we often set the Redis key in this format, e.g. “users:aaa” or “users:bbb” for grouping keys; that’s the same concept of region. The “region” here is “users”, and we can find all the Redis keys of the same region with pattern “users:*”.

 

The “key” is the MD5 hash of the request’s “HTTP method + URL” that is generated by the implementation (DefaultCacheKeyGenerator by default) of ICacheKeyGenerator. If we don’t implement our own cache key generator, our Redis key will be like this:


payment:5D828182FD57BC692F0055A01C27374F


The hash “5D828182FD57BC692F0055A01C27374F” is the “key”. The caching (read/write) won’t have any problem with the Redis key; however, if we want to delete the cache before it expires (manually or by another application), the hash will make it hard to delete the cache unless we know how to get the same hash value. Let’s implement our cache key generator at the next step.


 

Implement ICacheKeyGenerator


Before implementing our custom cache key generator, we have to think about the key pattern. Your Redis key may contain the information from the request’s URL parameter or from a HTTP header, etc. The way to generate a Redis key might be different by each upstreaming request or HTTP method.


The following sample code generates the “key” with the HTTP header “X-Redis-Key”. 


public class RedisKeyGenerator : ICacheKeyGenerator

{

    private const string RedisKeyHeaderName = "X-Redis-Key";


    public async ValueTask<string> GenerateRequestCacheKey(DownstreamRequest downstreamRequest, DownstreamRoute downstreamRoute)

    {

        StringBuilder customRedisKey = await this.TryGenRedisKeyByHttpHeader(downstreamRequest);


        if (customRedisKey.Length > 0)

        {

            string cacheKey = customRedisKey.ToString();

            return cacheKey;

        }

        else

        {

            #region Official implementation

            // You can copy the original Ocelot implementation here if the client doesn't give the HTTP header.

            #endregion

        }

    }


    private static Task<string> ReadContentAsync(DownstreamRequest downstream) => downstream.HasContent && downstream.Request?.Content != null

        ? downstream.Request.Content.ReadAsStringAsync() ?? Task.FromResult(string.Empty)

        : Task.FromResult(string.Empty);


    private Task<StringBuilder> TryGenRedisKeyByHttpHeader(DownstreamRequest downstreamRequest)

    {

        StringBuilder sbRedisKey = new();


        var httpHeaderValues = downstreamRequest.Headers.FirstOrDefault(x => x.Key.Equals(RedisKeyHeaderName));

        if (httpHeaderValues.Value != null && httpHeaderValues.Value.Any())

        {

            string headerValue = httpHeaderValues.Value.FirstOrDefault();

            if (!string.IsNullOrEmpty(headerValue))

            {

                sbRedisKey.Append(headerValue);

            }

        }

        return Task.FromResult(sbRedisKey);

    }

}

  

Don’t forget that we also put the “region” into our Redis key pattern “region:key” in RedisCacheStore. While we set the “Region” with value: “payment” and the request’s HTTP header “X-Redis-Key” is a payment transaction ID: “dedcf2fb-c8e0-4965-a590-d674e2094304”, then the Redis key will be


payment:dedcf2fb-c8e0-4965-a590-d674e2094304


The cache key seems more readable, because the client application knows the payment transaction ID and it can delete the cache anytime by itself. 



Register the new services


Now we can register our new services RedisKeyGenerator and RedisCacheStore into DI containers to let Ocelot use them instead of its default memory caching and cache key generator. 


 

 builder.Services.AddSingleton<ICacheKeyGenerator, RedisKeyGenerator>();

 builder.Services.AddSingleton<IOcelotCache<CachedResponse>, RedisCacheStore>();



The Cache Value 


The cache value by Ocelot contains:

  • Http status code

  • Http response’s headers

  • Http response body (in Base64) 



 localhost:6379> mget payment:dedcf2fb-c8e0-4965-a590-d674e2094304

 1) {"StatusCode":200,"Headers":{"Date":["Sat, 22 Mar 2025 16:45:22 GMT"],"Server":["Kestrel"],"Transfer-Encoding":["chunked"]},"ContentHeaders":{"Content-Type":["application/json; charset=utf-8"]},"Body":"eyJpZCI6IjkyMDQxNThiLTJhZjEtNDUxNS1iMTk4LTJlNjk5MmRmMjA4NiIsIml0ZW0iOiJEREQiLCJhbW91bnQiOjEwMCwiY3JlYXRlT24iOiIyMDI1LTAzLTE5VDAxOjQxOjQzLjQ5MjM1MzIrMDA6MDAifQ==","ReasonPhrase":"OK"}



By storing the cache in Redis and using the custom Redis key, we can easily enhance and manage the caching, or trouble-shooting.




Reference


Ocelot: Caching