@rafter-security/cli 0.5.3 → 0.5.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/agent/audit-skill.js +1 -1
- package/dist/commands/agent/baseline.js +203 -0
- package/dist/commands/agent/index.js +4 -0
- package/dist/commands/agent/init.js +5 -3
- package/dist/commands/agent/install-hook.js +41 -47
- package/dist/commands/agent/scan.js +40 -8
- package/dist/commands/agent/update-gitleaks.js +40 -0
- package/dist/commands/completion.js +308 -110
- package/dist/core/audit-logger.js +41 -0
- package/dist/core/config-defaults.js +4 -0
- package/dist/core/risk-rules.js +5 -3
- package/dist/index.js +1 -1
- package/dist/utils/binary-manager.js +52 -14
- package/package.json +1 -1
- package/resources/pre-push-hook.sh +60 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import fs from "fs";
|
|
2
|
+
import os from "os";
|
|
2
3
|
import path from "path";
|
|
3
4
|
import https from "https";
|
|
4
5
|
import { exec, execSync } from "child_process";
|
|
@@ -6,7 +7,7 @@ import { promisify } from "util";
|
|
|
6
7
|
import { getBinDir } from "../core/config-defaults.js";
|
|
7
8
|
import * as tar from "tar";
|
|
8
9
|
const execAsync = promisify(exec);
|
|
9
|
-
const GITLEAKS_VERSION = "8.18.2";
|
|
10
|
+
export const GITLEAKS_VERSION = "8.18.2";
|
|
10
11
|
export class BinaryManager {
|
|
11
12
|
constructor() {
|
|
12
13
|
this.binDir = getBinDir();
|
|
@@ -145,9 +146,11 @@ export class BinaryManager {
|
|
|
145
146
|
return lines.join("\n");
|
|
146
147
|
}
|
|
147
148
|
/**
|
|
148
|
-
* Download and install Gitleaks
|
|
149
|
+
* Download and install Gitleaks.
|
|
150
|
+
* @param onProgress Optional progress callback.
|
|
151
|
+
* @param version Gitleaks version to install (defaults to GITLEAKS_VERSION).
|
|
149
152
|
*/
|
|
150
|
-
async downloadGitleaks(onProgress) {
|
|
153
|
+
async downloadGitleaks(onProgress, version = GITLEAKS_VERSION) {
|
|
151
154
|
const log = onProgress || (() => { });
|
|
152
155
|
// Check platform support
|
|
153
156
|
if (!this.isPlatformSupported()) {
|
|
@@ -159,8 +162,8 @@ export class BinaryManager {
|
|
|
159
162
|
}
|
|
160
163
|
const platform = this.getPlatformString();
|
|
161
164
|
const arch = this.getArchString();
|
|
162
|
-
const url = this.getDownloadUrl(platform, arch);
|
|
163
|
-
log(`Downloading Gitleaks v${
|
|
165
|
+
const url = this.getDownloadUrl(platform, arch, version);
|
|
166
|
+
log(`Downloading Gitleaks v${version} for ${platform}/${arch}...`);
|
|
164
167
|
log(` URL: ${url}`);
|
|
165
168
|
const archivePath = path.join(this.binDir, platform === "windows" ? "gitleaks.zip" : "gitleaks.tar.gz");
|
|
166
169
|
try {
|
|
@@ -172,8 +175,7 @@ export class BinaryManager {
|
|
|
172
175
|
// Extract binary
|
|
173
176
|
log("Extracting binary...");
|
|
174
177
|
if (platform === "windows") {
|
|
175
|
-
|
|
176
|
-
throw new Error("Windows support coming soon");
|
|
178
|
+
await this.extractZip(archivePath);
|
|
177
179
|
}
|
|
178
180
|
else {
|
|
179
181
|
await this.extractTarball(archivePath);
|
|
@@ -255,15 +257,15 @@ export class BinaryManager {
|
|
|
255
257
|
throw new Error(`Unsupported architecture: ${arch}`);
|
|
256
258
|
}
|
|
257
259
|
/**
|
|
258
|
-
* Get download URL for platform/arch
|
|
260
|
+
* Get download URL for platform/arch/version
|
|
259
261
|
*/
|
|
260
|
-
getDownloadUrl(platform, arch) {
|
|
261
|
-
const baseUrl = `https://github.com/gitleaks/gitleaks/releases/download/v${
|
|
262
|
+
getDownloadUrl(platform, arch, version = GITLEAKS_VERSION) {
|
|
263
|
+
const baseUrl = `https://github.com/gitleaks/gitleaks/releases/download/v${version}`;
|
|
262
264
|
if (platform === "windows") {
|
|
263
|
-
return `${baseUrl}/gitleaks_${
|
|
265
|
+
return `${baseUrl}/gitleaks_${version}_windows_${arch}.zip`;
|
|
264
266
|
}
|
|
265
267
|
else {
|
|
266
|
-
return `${baseUrl}/gitleaks_${
|
|
268
|
+
return `${baseUrl}/gitleaks_${version}_${platform}_${arch}.tar.gz`;
|
|
267
269
|
}
|
|
268
270
|
}
|
|
269
271
|
/**
|
|
@@ -320,13 +322,49 @@ export class BinaryManager {
|
|
|
320
322
|
});
|
|
321
323
|
}
|
|
322
324
|
/**
|
|
323
|
-
* Extract
|
|
325
|
+
* Extract zip (Windows) — uses PowerShell's Expand-Archive, then copies
|
|
326
|
+
* only the gitleaks.exe binary to binDir. Cleans up the temp extract dir.
|
|
327
|
+
*/
|
|
328
|
+
async extractZip(zipPath) {
|
|
329
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "rafter-gitleaks-"));
|
|
330
|
+
try {
|
|
331
|
+
// PowerShell 5+ ships on all supported Windows versions
|
|
332
|
+
await execAsync(`powershell -NoProfile -Command "Expand-Archive -Force -LiteralPath '${zipPath}' -DestinationPath '${tempDir}'"`, { timeout: 30000 });
|
|
333
|
+
// Find gitleaks.exe — may be at root or inside a subdirectory
|
|
334
|
+
const findBinary = (dir) => {
|
|
335
|
+
for (const entry of fs.readdirSync(dir)) {
|
|
336
|
+
const full = path.join(dir, entry);
|
|
337
|
+
if (entry === "gitleaks.exe")
|
|
338
|
+
return full;
|
|
339
|
+
if (fs.statSync(full).isDirectory()) {
|
|
340
|
+
const found = findBinary(full);
|
|
341
|
+
if (found)
|
|
342
|
+
return found;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
return null;
|
|
346
|
+
};
|
|
347
|
+
const found = findBinary(tempDir);
|
|
348
|
+
if (!found)
|
|
349
|
+
throw new Error("gitleaks.exe not found in archive");
|
|
350
|
+
fs.copyFileSync(found, path.join(this.binDir, "gitleaks.exe"));
|
|
351
|
+
}
|
|
352
|
+
finally {
|
|
353
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* Extract tarball — binary only, strip packaging extras (LICENSE, README.md).
|
|
358
|
+
*
|
|
359
|
+
* The gitleaks release tarball has all files at the archive root (no top-level
|
|
360
|
+
* directory), so strip: 0 (the default). With strip: 1, node-tar reduces the
|
|
361
|
+
* single-component paths to empty strings; the filter never matches "gitleaks"
|
|
362
|
+
* and nothing is extracted. The filter alone is sufficient.
|
|
324
363
|
*/
|
|
325
364
|
async extractTarball(tarballPath) {
|
|
326
365
|
await tar.extract({
|
|
327
366
|
file: tarballPath,
|
|
328
367
|
cwd: this.binDir,
|
|
329
|
-
strip: 1,
|
|
330
368
|
filter: (p) => {
|
|
331
369
|
const base = path.basename(p);
|
|
332
370
|
return base === "gitleaks" || base === "gitleaks.exe";
|
package/package.json
CHANGED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Rafter Security Pre-Push Hook
|
|
3
|
+
# Scans commits being pushed for secrets
|
|
4
|
+
|
|
5
|
+
# Colors for output
|
|
6
|
+
RED='\033[0;31m'
|
|
7
|
+
YELLOW='\033[1;33m'
|
|
8
|
+
GREEN='\033[0;32m'
|
|
9
|
+
NC='\033[0m' # No Color
|
|
10
|
+
|
|
11
|
+
# Check if rafter is installed
|
|
12
|
+
if ! command -v rafter &> /dev/null; then
|
|
13
|
+
echo -e "${YELLOW}⚠️ Warning: rafter CLI not found in PATH${NC}"
|
|
14
|
+
echo " Install: npm install -g @rafter-security/cli"
|
|
15
|
+
echo " Skipping secret scan..."
|
|
16
|
+
exit 0
|
|
17
|
+
fi
|
|
18
|
+
|
|
19
|
+
ZERO_SHA="0000000000000000000000000000000000000000"
|
|
20
|
+
FOUND_SECRETS=0
|
|
21
|
+
|
|
22
|
+
while read local_ref local_sha remote_ref remote_sha; do
|
|
23
|
+
# Skip branch deletions
|
|
24
|
+
if [ "$local_sha" = "$ZERO_SHA" ]; then
|
|
25
|
+
continue
|
|
26
|
+
fi
|
|
27
|
+
|
|
28
|
+
if [ "$remote_sha" = "$ZERO_SHA" ]; then
|
|
29
|
+
# New branch — scan all commits on this branch not on any remote branch
|
|
30
|
+
ref_arg=$(git rev-list --max-parents=0 "$local_sha" 2>/dev/null | head -1)
|
|
31
|
+
if [ -z "$ref_arg" ]; then
|
|
32
|
+
ref_arg="$local_sha^"
|
|
33
|
+
fi
|
|
34
|
+
else
|
|
35
|
+
# Existing branch — scan only new commits
|
|
36
|
+
ref_arg="$remote_sha"
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
echo "🔍 Rafter: Scanning commits being pushed ($local_ref)..."
|
|
40
|
+
|
|
41
|
+
rafter agent scan --diff "$ref_arg" --quiet
|
|
42
|
+
EXIT_CODE=$?
|
|
43
|
+
|
|
44
|
+
if [ $EXIT_CODE -ne 0 ]; then
|
|
45
|
+
FOUND_SECRETS=1
|
|
46
|
+
fi
|
|
47
|
+
done
|
|
48
|
+
|
|
49
|
+
if [ $FOUND_SECRETS -ne 0 ]; then
|
|
50
|
+
echo -e "${RED}❌ Push blocked: Secrets detected in commits being pushed${NC}"
|
|
51
|
+
echo ""
|
|
52
|
+
echo " Run: rafter agent scan --diff <remote-sha>"
|
|
53
|
+
echo " To see details and remediate."
|
|
54
|
+
echo ""
|
|
55
|
+
echo " To bypass (NOT recommended): git push --no-verify"
|
|
56
|
+
exit 1
|
|
57
|
+
fi
|
|
58
|
+
|
|
59
|
+
echo -e "${GREEN}✓ No secrets detected${NC}"
|
|
60
|
+
exit 0
|