Gitleaks is a SAST (Static Application Security Testing) tool for detecting hardcoded secrets like passwords, API keys, and tokens in git repositories. This comprehensive guide covers Java-based configuration management, custom rules, and integration patterns.
Understanding Gitleaks Configuration
Key Configuration Components:
- Rules: Define patterns for secret detection
- Allowlists: Specify false positives to ignore
- Entropy checks: Detect high-entropy strings that might be secrets
- Path filtering: Include/exclude specific files and directories
Java Dependencies Setup
<dependencies> <!-- JSON processing for gitleaks config --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.16.1</version> </dependency> <!-- YAML processing --> <dependency> <groupId>com.fasterxml.jackson.dataformat</groupId> <artifactId>jackson-dataformat-yaml</artifactId> <version>2.16.1</version> </dependency> <!-- File system operations --> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <Version>2.14.0</version> </dependency> <!-- Process execution for running gitleaks --> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-exec</artifactId> <version>1.3</version> </dependency> </dependencies>
Gitleaks Configuration Model Classes
package com.example.gitleaks;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@JsonInclude(JsonInclude.Include.NON_NULL)
public class GitleaksConfig {
private String title;
private String description;
private List<Rule> rules;
private Allowlist allowlist;
private Map<String, String> comments;
// Constructors
public GitleaksConfig() {
this.rules = new ArrayList<>();
this.allowlist = new Allowlist();
}
public GitleaksConfig(String title, String description) {
this();
this.title = title;
this.description = description;
}
// Getters and setters
public String getTitle() { return title; }
public void setTitle(String title) { this.title = title; }
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
public List<Rule> getRules() { return rules; }
public void setRules(List<Rule> rules) { this.rules = rules; }
public Allowlist getAllowlist() { return allowlist; }
public void setAllowlist(Allowlist allowlist) { this.allowlist = allowlist; }
public Map<String, String> getComments() { return comments; }
public void setComments(Map<String, String> comments) { this.comments = comments; }
// Builder methods
public GitleaksConfig addRule(Rule rule) {
this.rules.add(rule);
return this;
}
public GitleaksConfig withAllowlist(Allowlist allowlist) {
this.allowlist = allowlist;
return this;
}
@JsonInclude(JsonInclude.Include.NON_NULL)
public static class Rule {
private String id;
private String description;
private String regex;
private List<String> keywords;
private String path;
private List<String> tags;
private Map<String, String> entropy;
private Boolean allowlist;
// Constructors
public Rule() {}
public Rule(String id, String description, String regex) {
this.id = id;
this.description = description;
this.regex = regex;
this.keywords = new ArrayList<>();
this.tags = new ArrayList<>();
}
// Getters and setters
public String getId() { return id; }
public void setId(String id) { this.id = id; }
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
public String getRegex() { return regex; }
public void setRegex(String regex) { this.regex = regex; }
public List<String> getKeywords() { return keywords; }
public void setKeywords(List<String> keywords) { this.keywords = keywords; }
public String getPath() { return path; }
public void setPath(String path) { this.path = path; }
public List<String> getTags() { return tags; }
public void setTags(List<String> tags) { this.tags = tags; }
public Map<String, String> getEntropy() { return entropy; }
public void setEntropy(Map<String, String> entropy) { this.entropy = entropy; }
public Boolean getAllowlist() { return allowlist; }
public void setAllowlist(Boolean allowlist) { this.allowlist = allowlist; }
// Builder methods
public Rule withKeywords(List<String> keywords) {
this.keywords = keywords;
return this;
}
public Rule withPath(String path) {
this.path = path;
return this;
}
public Rule withTags(List<String> tags) {
this.tags = tags;
return this;
}
public Rule withEntropy(String min, String max) {
this.entropy = Map.of("min", min, "max", max);
return this;
}
}
@JsonInclude(JsonInclude.Include.NON_NULL)
public static class Allowlist {
private List<String> description;
private List<String> files;
private List<String> paths;
private List<String> regexes;
private List<String> commits;
private List<String> repos;
// Constructors
public Allowlist() {
this.description = new ArrayList<>();
this.files = new ArrayList<>();
this.paths = new ArrayList<>();
this.regexes = new ArrayList<>();
this.commits = new ArrayList<>();
this.repos = new ArrayList<>();
}
// Getters and setters
public List<String> getDescription() { return description; }
public void setDescription(List<String> description) { this.description = description; }
public List<String> getFiles() { return files; }
public void setFiles(List<String> files) { this.files = files; }
public List<String> getPaths() { return paths; }
public void setPaths(List<String> paths) { this.paths = paths; }
public List<String> getRegexes() { return regexes; }
public void setRegexes(List<String> regexes) { this.regexes = regexes; }
public List<String> getCommits() { return commits; }
public void setCommits(List<String> commits) { this.commits = commits; }
public List<String> getRepos() { return repos; }
public void setRepos(List<String> repos) { this.repos = repos; }
// Builder methods
public Allowlist withDescription(List<String> description) {
this.description = description;
return this;
}
public Allowlist withFiles(List<String> files) {
this.files = files;
return this;
}
public Allowlist withPaths(List<String> paths) {
this.paths = paths;
return this;
}
public Allowlist withRegexes(List<String> regexes) {
this.regexes = regexes;
return this;
}
}
}
Gitleaks Configuration Manager
package com.example.gitleaks;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import org.apache.commons.exec.CommandLine;
import org.apache.commons.exec.DefaultExecutor;
import org.apache.commons.exec.PumpStreamHandler;
import org.apache.commons.io.FileUtils;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
public class GitleaksConfigManager {
private final ObjectMapper jsonMapper;
private final ObjectMapper yamlMapper;
private final String gitleaksPath;
public GitleaksConfigManager() {
this.jsonMapper = new ObjectMapper();
this.yamlMapper = new ObjectMapper(new YAMLFactory());
this.gitleaksPath = System.getenv().getOrDefault("GITLEAKS_PATH", "gitleaks");
}
public GitleaksConfigManager(String gitleaksPath) {
this();
this.gitleaksPath = gitleaksPath;
}
/**
* Create a default Gitleaks configuration with common rules
*/
public GitleaksConfig createDefaultConfig() {
GitleaksConfig config = new GitleaksConfig(
"Custom Java Gitleaks Configuration",
"Automatically generated Gitleaks configuration for Java projects"
);
// AWS Secrets
config.addRule(new GitleaksConfig.Rule(
"aws-access-key-id",
"AWS Access Key ID",
"(A3T[A-Z0-9]|AKIA|AGPA|AIDA|AROA|AIPA|ANPA|ANVA|ASIA)[A-Z0-9]{16}"
).withTags(Arrays.asList("aws", "key")));
// AWS Secret Access Key
config.addRule(new GitleaksConfig.Rule(
"aws-secret-access-key",
"AWS Secret Access Key",
"(?i)aws(.{0,20})?['\"][0-9a-zA-Z\\/+]{40}['\"]"
).withTags(Arrays.asList("aws", "key")));
// GitHub Personal Access Token
config.addRule(new GitleaksConfig.Rule(
"github-token",
"GitHub Personal Access Token",
"(?i)(github|gh|pat).{0,20}['\"][0-9a-zA-Z]{35,40}['\"]"
).withTags(Arrays.asList("github", "token")));
// Generic API Key
config.addRule(new GitleaksConfig.Rule(
"generic-api-key",
"Generic API Key",
"(?i)(api[_-]?key|apikey).{0,20}['\"][0-9a-zA-Z]{32,45}['\"]"
).withTags(Arrays.asList("api", "key")));
// Database URLs with passwords
config.addRule(new GitleaksConfig.Rule(
"database-url",
"Database Connection String with Password",
"([a-zA-Z]+)://[^:]+:[^@]+@[^/]+/[^?]+"
).withTags(Arrays.asList("database", "password")));
// JWT tokens (high entropy)
config.addRule(new GitleaksConfig.Rule(
"jwt-token",
"JSON Web Token",
"eyJ[a-zA-Z0-9]{10,}\\.[a-zA-Z0-9_-]{10,}\\.[a-zA-Z0-9_-]{10,}"
).withEntropy("4.5", "8.0").withTags(Arrays.asList("jwt", "token")));
// SSH Private Keys
config.addRule(new GitleaksConfig.Rule(
"ssh-private-key",
"SSH Private Key",
"-----BEGIN [A-Z]* PRIVATE KEY-----"
).withTags(Arrays.asList("ssh", "private-key")));
// Configure allowlist
GitleaksConfig.Allowlist allowlist = new GitleaksConfig.Allowlist();
allowlist.setFiles(Arrays.asList(
"package-lock.json",
"yarn.lock",
"go.sum",
"pom.xml"
));
allowlist.setPaths(Arrays.asList(
"**/test/**",
"**/vendor/**",
"**/node_modules/**"
));
allowlist.setRegexes(Arrays.asList(
"example.*key",
"test.*password"
));
config.setAllowlist(allowlist);
return config;
}
/**
* Create Java-specific configuration
*/
public GitleaksConfig createJavaProjectConfig() {
GitleaksConfig config = createDefaultConfig();
// Add Java-specific rules
config.addRule(new GitleaksConfig.Rule(
"java-properties-password",
"Java Properties File Password",
"(?i).*\\.(properties|yml|yaml|conf|config).*password.*=.*[\\w@!$%^&*()]{8,}"
).withPath("\\.(properties|yml|yaml|conf|config)$"));
// Spring Boot application properties
config.addRule(new GitleaksConfig.Rule(
"spring-boot-password",
"Spring Boot Application Password",
"(?i)spring\\.datasource\\.password.*=.*[\\w@!$%^&*()]{8,}"
).withPath("application.*\\.(properties|yml|yaml)$"));
// Maven settings.xml passwords
config.addRule(new GitleaksConfig.Rule(
"maven-password",
"Maven Settings Password",
"<password>[^<]+</password>"
).withPath("settings\\.xml$"));
// Update allowlist for Java projects
GitleaksConfig.Allowlist javaAllowlist = config.getAllowlist();
javaAllowlist.getFiles().addAll(Arrays.asList(
"pom.xml",
"build.gradle",
"settings.gradle",
"gradle.properties"
));
javaAllowlist.getPaths().addAll(Arrays.asList(
"**/target/**",
"**/build/**",
"**/.gradle/**"
));
return config;
}
/**
* Save configuration to file
*/
public void saveConfigToFile(GitleaksConfig config, String filePath, boolean asYaml)
throws IOException {
File file = new File(filePath);
if (asYaml) {
yamlMapper.writeValue(file, config);
} else {
jsonMapper.writeValue(file, config);
}
}
/**
* Load configuration from file
*/
public GitleaksConfig loadConfigFromFile(String filePath) throws IOException {
File file = new File(filePath);
if (filePath.endsWith(".yml") || filePath.endsWith(".yaml")) {
return yamlMapper.readValue(file, GitleaksConfig.class);
} else {
return jsonMapper.readValue(file, GitleaksConfig.class);
}
}
/**
* Run Gitleaks scan with custom configuration
*/
public GitleaksScanResult runScan(String repoPath, String configPath,
Map<String, String> additionalOptions) throws IOException {
CommandLine cmdLine = new CommandLine(gitleaksPath);
cmdLine.addArgument("detect");
cmdLine.addArgument("--source");
cmdLine.addArgument(repoPath);
cmdLine.addArgument("--config");
cmdLine.addArgument(configPath);
cmdLine.addArgument("--verbose");
cmdLine.addArgument("--report-format");
cmdLine.addArgument("json");
// Add additional options
if (additionalOptions != null) {
additionalOptions.forEach((key, value) -> {
cmdLine.addArgument(key);
if (value != null && !value.isEmpty()) {
cmdLine.addArgument(value);
}
});
}
DefaultExecutor executor = new DefaultExecutor();
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ByteArrayOutputStream errorStream = new ByteArrayOutputStream();
executor.setStreamHandler(new PumpStreamHandler(outputStream, errorStream));
try {
int exitValue = executor.execute(cmdLine);
String output = outputStream.toString();
String error = errorStream.toString();
return new GitleaksScanResult(exitValue, output, error, exitValue == 0);
} catch (IOException e) {
throw new IOException("Gitleaks execution failed: " + e.getMessage(), e);
}
}
/**
* Run Gitleaks scan with in-memory configuration
*/
public GitleaksScanResult runScanWithConfig(String repoPath, GitleaksConfig config)
throws IOException {
// Create temporary config file
File tempConfig = File.createTempFile("gitleaks-config-", ".toml");
saveConfigToFile(config, tempConfig.getAbsolutePath(), false);
tempConfig.deleteOnExit();
return runScan(repoPath, tempConfig.getAbsolutePath(), null);
}
/**
* Validate configuration file
*/
public boolean validateConfig(String configPath) throws IOException {
try {
GitleaksConfig config = loadConfigFromFile(configPath);
return config != null && config.getRules() != null;
} catch (Exception e) {
return false;
}
}
public static class GitleaksScanResult {
private final int exitCode;
private final String output;
private final String error;
private final boolean success;
private List<SecretFinding> findings;
public GitleaksScanResult(int exitCode, String output, String error, boolean success) {
this.exitCode = exitCode;
this.output = output;
this.error = error;
this.success = success;
this.findings = new ArrayList<>();
parseFindings();
}
private void parseFindings() {
if (success || output == null || output.trim().isEmpty()) {
return;
}
try {
// Parse JSON output from gitleaks
// This is a simplified parsing - in reality, you'd use proper JSON parsing
String[] lines = output.split("\n");
for (String line : lines) {
if (line.contains("description") && line.contains("line")) {
// Extract finding details
SecretFinding finding = new SecretFinding();
// Parse line, file, description, etc.
findings.add(finding);
}
}
} catch (Exception e) {
// Log parsing error but don't fail
System.err.println("Failed to parse gitleaks findings: " + e.getMessage());
}
}
// Getters
public int getExitCode() { return exitCode; }
public String getOutput() { return output; }
public String getError() { return error; }
public boolean isSuccess() { return success; }
public List<SecretFinding> getFindings() { return findings; }
}
public static class SecretFinding {
private String description;
private String file;
private String line;
private String secret;
private String ruleId;
private String commit;
// Getters and setters
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
public String getFile() { return file; }
public void setFile(String file) { this.file = file; }
public String getLine() { return line; }
public void setLine(String line) { this.line = line; }
public String getSecret() { return secret; }
public void setSecret(String secret) { this.secret = secret; }
public String getRuleId() { return ruleId; }
public void setRuleId(String ruleId) { this.ruleId = ruleId; }
public String getCommit() { return commit; }
public void setCommit(String commit) { this.commit = commit; }
}
}
Spring Boot Integration Service
package com.example.gitleaks;
import org.springframework.stereotype.Service;
import org.springframework.beans.factory.annotation.Value;
import java.io.File;
import java.io.IOException;
import java.util.*;
@Service
public class GitleaksService {
private final GitleaksConfigManager configManager;
@Value("${gitleaks.config.path:./gitleaks-config.yml}")
private String defaultConfigPath;
@Value("${gitleaks.report.directory:./reports}")
private String reportDirectory;
public GitleaksService() {
this.configManager = new GitleaksConfigManager();
}
/**
* Generate project-specific configuration
*/
public GitleaksConfig generateProjectConfig(String projectType,
Map<String, Object> customRules) {
GitleaksConfig config;
switch (projectType.toLowerCase()) {
case "java":
config = configManager.createJavaProjectConfig();
break;
case "nodejs":
config = createNodeJSConfig();
break;
case "python":
config = createPythonConfig();
break;
default:
config = configManager.createDefaultConfig();
}
// Add custom rules if provided
if (customRules != null) {
addCustomRules(config, customRules);
}
return config;
}
/**
* Scan repository and return results
*/
public GitleaksScanResult scanRepository(String repositoryPath,
String configPath) throws IOException {
File repoDir = new File(repositoryPath);
if (!repoDir.exists() || !repoDir.isDirectory()) {
throw new IllegalArgumentException("Repository path does not exist: " + repositoryPath);
}
File configFile = new File(configPath);
if (!configFile.exists()) {
// Generate default config if none exists
GitleaksConfig config = configManager.createDefaultConfig();
configManager.saveConfigToFile(config, configPath, true);
}
Map<String, String> options = new HashMap<>();
options.put("--report-path", reportDirectory + "/gitleaks-report.json");
return configManager.runScan(repositoryPath, configPath, options);
}
/**
* Scan repository with auto-generated configuration
*/
public GitleaksScanResult scanRepositoryWithAutoConfig(String repositoryPath,
String projectType) throws IOException {
GitleaksConfig config = generateProjectConfig(projectType, null);
return configManager.runScanWithConfig(repositoryPath, config);
}
/**
* Create CI/CD pipeline scan
*/
public CICDScanResult runCICDScan(String repositoryPath, String projectType) {
try {
GitleaksScanResult result = scanRepositoryWithAutoConfig(repositoryPath, projectType);
CICDScanResult cicdResult = new CICDScanResult();
cicdResult.setSuccess(result.isSuccess());
cicdResult.setFindingsCount(result.getFindings().size());
cicdResult.setOutput(result.getOutput());
cicdResult.setError(result.getError());
if (!result.isSuccess()) {
cicdResult.setBlockPipeline(true);
cicdResult.setMessage("Secrets detected in code. Pipeline blocked.");
} else {
cicdResult.setBlockPipeline(false);
cicdResult.setMessage("No secrets detected. Pipeline continues.");
}
return cicdResult;
} catch (IOException e) {
CICDScanResult errorResult = new CICDScanResult();
errorResult.setSuccess(false);
errorResult.setBlockPipeline(true);
errorResult.setMessage("Gitleaks scan failed: " + e.getMessage());
return errorResult;
}
}
/**
* Generate configuration file for project
*/
public void generateConfigFile(String projectType, String outputPath) throws IOException {
GitleaksConfig config = generateProjectConfig(projectType, null);
boolean isYaml = outputPath.endsWith(".yml") || outputPath.endsWith(".yaml");
configManager.saveConfigToFile(config, outputPath, isYaml);
}
private GitleaksConfig createNodeJSConfig() {
GitleaksConfig config = configManager.createDefaultConfig();
// Add Node.js specific rules
config.addRule(new GitleaksConfig.Rule(
"npm-token",
"NPM Access Token",
"npm_[a-zA-Z0-9]{36}"
).withTags(Arrays.asList("npm", "token")));
config.addRule(new GitleaksConfig.Rule(
"node-env-password",
"Node.js Environment Password",
"(?i)process\\.env\\.[A-Z_]+.*=.*['\"][\\w@!$%^&*()]{8,}['\"]"
));
return config;
}
private GitleaksConfig createPythonConfig() {
GitleaksConfig config = configManager.createDefaultConfig();
// Add Python specific rules
config.addRule(new GitleaksConfig.Rule(
"python-dictionary-password",
"Python Dictionary Password",
"(?i)['\"]password['\"]\\s*:\\s*['\"][\\w@!$%^&*()]{8,}['\"]"
));
config.addRule(new GitleaksConfig.Rule(
"django-secret-key",
"Django Secret Key",
"SECRET_KEY\\s*=\\s*['\"][\\w@!$%^&*()]{50,}['\"]"
));
return config;
}
private void addCustomRules(GitleaksConfig config, Map<String, Object> customRules) {
// Implementation for adding custom rules from map
// This would parse the custom rules and add them to the configuration
}
public static class CICDScanResult {
private boolean success;
private boolean blockPipeline;
private String message;
private int findingsCount;
private String output;
private String error;
// Getters and setters
public boolean isSuccess() { return success; }
public void setSuccess(boolean success) { this.success = success; }
public boolean isBlockPipeline() { return blockPipeline; }
public void setBlockPipeline(boolean blockPipeline) { this.blockPipeline = blockPipeline; }
public String getMessage() { return message; }
public void setMessage(String message) { this.message = message; }
public int getFindingsCount() { return findingsCount; }
public void setFindingsCount(int findingsCount) { this.findingsCount = findingsCount; }
public String getOutput() { return output; }
public void setOutput(String output) { this.output = output; }
public String getError() { return error; }
public void setError(String error) { this.error = error; }
}
}
Spring Boot REST Controller
package com.example.gitleaks;
import org.springframework.web.bind.annotation.*;
import org.springframework.http.ResponseEntity;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/api/gitleaks")
public class GitleaksController {
private final GitleaksService gitleaksService;
public GitleaksController(GitleaksService gitleaksService) {
this.gitleaksService = gitleaksService;
}
@PostMapping("/scan")
public ResponseEntity<Map<String, Object>> scanRepository(
@RequestBody ScanRequest request) {
try {
GitleaksService.GitleaksScanResult result =
gitleaksService.scanRepository(request.getRepositoryPath(),
request.getConfigPath());
Map<String, Object> response = new HashMap<>();
response.put("success", result.isSuccess());
response.put("findingsCount", result.getFindings().size());
response.put("findings", result.getFindings());
response.put("output", result.getOutput());
if (!result.isSuccess()) {
response.put("error", result.getError());
return ResponseEntity.ok(response);
}
return ResponseEntity.ok(response);
} catch (IOException e) {
return ResponseEntity.badRequest()
.body(Map.of("error", "Scan failed: " + e.getMessage()));
}
}
@PostMapping("/scan/cicd")
public ResponseEntity<GitleaksService.CICDScanResult> runCICDScan(
@RequestBody CICDScanRequest request) {
GitleaksService.CICDScanResult result =
gitleaksService.runCICDScan(request.getRepositoryPath(),
request.getProjectType());
return ResponseEntity.ok(result);
}
@PostMapping("/config/generate")
public ResponseEntity<Map<String, String>> generateConfig(
@RequestBody GenerateConfigRequest request) {
try {
gitleaksService.generateConfigFile(request.getProjectType(),
request.getOutputPath());
return ResponseEntity.ok(Map.of(
"message", "Configuration generated successfully",
"path", request.getOutputPath()
));
} catch (IOException e) {
return ResponseEntity.badRequest()
.body(Map.of("error", "Config generation failed: " + e.getMessage()));
}
}
@GetMapping("/health")
public ResponseEntity<Map<String, String>> health() {
return ResponseEntity.ok(Map.of("status", "healthy"));
}
// Request DTOs
public static class ScanRequest {
private String repositoryPath;
private String configPath;
// Getters and setters
public String getRepositoryPath() { return repositoryPath; }
public void setRepositoryPath(String repositoryPath) { this.repositoryPath = repositoryPath; }
public String getConfigPath() { return configPath; }
public void setConfigPath(String configPath) { this.configPath = configPath; }
}
public static class CICDScanRequest {
private String repositoryPath;
private String projectType;
// Getters and setters
public String getRepositoryPath() { return repositoryPath; }
public void setRepositoryPath(String repositoryPath) { this.repositoryPath = repositoryPath; }
public String getProjectType() { return projectType; }
public void setProjectType(String projectType) { this.projectType = projectType; }
}
public static class GenerateConfigRequest {
private String projectType;
private String outputPath;
// Getters and setters
public String getProjectType() { return projectType; }
public void setProjectType(String projectType) { this.projectType = projectType; }
public String getOutputPath() { return outputPath; }
public void setOutputPath(String outputPath) { this.outputPath = outputPath; }
}
}
Git Hook Integration
package com.example.gitleaks;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
public class GitHookManager {
private final GitleaksConfigManager configManager;
public GitHookManager() {
this.configManager = new GitleaksConfigManager();
}
/**
* Install pre-commit hook for a repository
*/
public boolean installPreCommitHook(String repositoryPath) throws IOException {
Path hookPath = Paths.get(repositoryPath, ".git", "hooks", "pre-commit");
// Create hooks directory if it doesn't exist
Files.createDirectories(hookPath.getParent());
// Create pre-commit hook script
String hookScript = createPreCommitHookScript();
try (FileWriter writer = new FileWriter(hookPath.toFile())) {
writer.write(hookScript);
}
// Make the hook executable
hookPath.toFile().setExecutable(true);
return hookPath.toFile().exists();
}
private String createPreCommitHookScript() {
return "#!/bin/bash\n" +
"\n" +
"# Gitleaks Pre-commit Hook\n" +
"echo \"Running Gitleaks secret detection...\"\n" +
"\n" +
"# Run gitleaks on staged files\n" +
"gitleaks detect --source . --staged --verbose\n" +
"\n" +
"if [ $? -ne 0 ]; then\n" +
" echo \"❌ Gitleaks found secrets in your code. Commit blocked.\"\n" +
" echo \"Please remove any sensitive information before committing.\"\n" +
" exit 1\n" +
"else\n" +
" echo \"✅ No secrets detected. Proceeding with commit.\"\n" +
" exit 0\n" +
"fi\n";
}
/**
* Create Java-based pre-commit hook
*/
public boolean installJavaPreCommitHook(String repositoryPath,
GitleaksConfig config) throws IOException {
Path hookPath = Paths.get(repositoryPath, ".git", "hooks", "pre-commit");
String javaHookScript = createJavaPreCommitHookScript(config);
try (FileWriter writer = new FileWriter(hookPath.toFile())) {
writer.write(javaHookScript);
}
hookPath.toFile().setExecutable(true);
return hookPath.toFile().exists();
}
private String createJavaPreCommitHookScript(GitleaksConfig config) {
// This would create a script that uses the Java application to run the scan
// Simplified version
return "#!/bin/bash\n" +
"\n" +
"# Java Gitleaks Pre-commit Hook\n" +
"echo \"Running Java Gitleaks scan...\"\n" +
"\n" +
"# Call your Java application\n" +
"java -jar gitleaks-integration.jar --scan-staged\n" +
"\n" +
"exit $?\n";
}
}
Best Practices for Production
- Custom Rules: Create project-specific rules for your organization's secrets patterns
- CI/CD Integration: Integrate Gitleaks scans in your pipeline to block commits with secrets
- Regular Updates: Keep Gitleaks and your configuration updated with new secret patterns
- False Positive Management: Use allowlists strategically to reduce noise
- Historical Scans: Run Gitleaks on git history to find existing secrets
- Education: Combine tooling with developer education about secret management
- Monitoring: Set up alerts for detected secrets in production scans
Conclusion
Implementing Gitleaks configuration management in Java provides a robust solution for detecting and preventing secret leakage in your codebase. The Java-based approach offers:
- Dynamic Configuration: Generate project-specific configurations programmatically
- CI/CD Integration: Seamlessly integrate secret detection into your pipeline
- Custom Rules: Easily add organization-specific secret patterns
- Centralized Management: Manage configurations across multiple projects
- Extensible Architecture: Easy to extend with new rule types and scanners
By integrating Gitleaks into your development workflow through Java-based configuration management, you can significantly reduce the risk of credential leakage and enhance your overall security posture.