laravel-security-agent 1.3.0 → 1.3.2
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/package.json
CHANGED
|
@@ -121,6 +121,15 @@ final class GitHistorySanitizationSkill implements SkillInterface
|
|
|
121
121
|
// ── Phase 3: Discover sensitive files in history ─────────────────────
|
|
122
122
|
$sensitiveFiles = $this->findSensitiveFilesInHistory($repoPath);
|
|
123
123
|
|
|
124
|
+
// ── Phase 3b: Deep audit deploy.php files found in history ───────────
|
|
125
|
+
$deployPhpFiles = array_values(array_filter(
|
|
126
|
+
$sensitiveFiles,
|
|
127
|
+
fn (string $f): bool => preg_match('/deploy\.php$/i', $f) === 1
|
|
128
|
+
));
|
|
129
|
+
$deployPhpAudit = !empty($deployPhpFiles)
|
|
130
|
+
? $this->auditDeployPhpInHistory($repoPath, $deployPhpFiles)
|
|
131
|
+
: [];
|
|
132
|
+
|
|
124
133
|
// ── Phase 4: Generate sanitizer script ───────────────────────────────
|
|
125
134
|
$script = $this->buildSanitizerScript($repoPath, $backupBranch, $sensitiveFiles);
|
|
126
135
|
file_put_contents($scriptDest, $script);
|
|
@@ -140,22 +149,28 @@ final class GitHistorySanitizationSkill implements SkillInterface
|
|
|
140
149
|
|
|
141
150
|
// ── Build response ────────────────────────────────────────────────────
|
|
142
151
|
$totalSecrets = array_sum(array_map('count', $secretFindings));
|
|
152
|
+
$deployRisk = array_sum(array_map(
|
|
153
|
+
fn (array $d): int => count($d['server_ips']) + count($d['server_paths']),
|
|
154
|
+
$deployPhpAudit
|
|
155
|
+
));
|
|
143
156
|
|
|
144
157
|
return [
|
|
145
158
|
'summary' => implode(' | ', [
|
|
146
159
|
count($gitignoreFindings) . ' gitignore gaps',
|
|
147
160
|
$totalSecrets . ' secret occurrences across ' . count($secretFindings) . ' pattern types',
|
|
148
161
|
count($sensitiveFiles) . ' sensitive files in history',
|
|
162
|
+
count($deployPhpFiles) . ' deploy.php file(s) — ' . $deployRisk . ' server exposure(s)',
|
|
149
163
|
$dryRun ? 'DRY RUN — script generated only' : 'REWRITE EXECUTED',
|
|
150
164
|
]),
|
|
151
|
-
'backup_branch_exists'
|
|
152
|
-
'dryRun'
|
|
153
|
-
'gitignore_gaps'
|
|
154
|
-
'secret_findings'
|
|
165
|
+
'backup_branch_exists' => $backupExists,
|
|
166
|
+
'dryRun' => $dryRun,
|
|
167
|
+
'gitignore_gaps' => $gitignoreFindings,
|
|
168
|
+
'secret_findings' => $secretFindings,
|
|
155
169
|
'sensitive_files_history' => $sensitiveFiles,
|
|
156
|
-
'
|
|
157
|
-
'
|
|
158
|
-
'
|
|
170
|
+
'deploy_php_audit' => $deployPhpAudit,
|
|
171
|
+
'generated_script_path' => $scriptDest,
|
|
172
|
+
'execution_result' => $executionResult,
|
|
173
|
+
'next_steps' => $this->buildNextSteps($backupExists, $backupBranch, $dryRun, $scriptDest),
|
|
159
174
|
];
|
|
160
175
|
}
|
|
161
176
|
|
|
@@ -294,6 +309,91 @@ final class GitHistorySanitizationSkill implements SkillInterface
|
|
|
294
309
|
return array_values($sensitive);
|
|
295
310
|
}
|
|
296
311
|
|
|
312
|
+
/**
|
|
313
|
+
* For each deploy.php found in history, retrieve its blob content and
|
|
314
|
+
* extract any server IP addresses and server folder paths exposed.
|
|
315
|
+
*
|
|
316
|
+
* Uses `git log --all -- <file>` to list commits, then `git show <commit>:<file>`
|
|
317
|
+
* to read the actual file content at that point in time.
|
|
318
|
+
*
|
|
319
|
+
* @param string[] $deployFiles Relative paths matching deploy.php in git history.
|
|
320
|
+
* @return array<int, array{
|
|
321
|
+
* file: string,
|
|
322
|
+
* commit: string,
|
|
323
|
+
* server_ips: string[],
|
|
324
|
+
* server_paths: string[]
|
|
325
|
+
* }>
|
|
326
|
+
*/
|
|
327
|
+
private function auditDeployPhpInHistory(string $repoPath, array $deployFiles): array
|
|
328
|
+
{
|
|
329
|
+
// Matches public IPs — excludes loopback (127.), link-local (169.254.),
|
|
330
|
+
// and RFC-1918 private ranges (10., 172.16-31., 192.168.) which are
|
|
331
|
+
// internal infrastructure and not staging/production exposures.
|
|
332
|
+
$ipPattern = '/\b(?!127\.|169\.254\.|10\.|172\.(?:1[6-9]|2\d|3[01])\.|192\.168\.)' .
|
|
333
|
+
'(?:\d{1,3}\.){3}\d{1,3}\b/';
|
|
334
|
+
|
|
335
|
+
// Matches common Deployer/Envoy server path keys and bare Unix paths.
|
|
336
|
+
$pathPattern = '/(?:deploy_path|current_path|release_path|app_path|root_path|upload_path)' .
|
|
337
|
+
'\s*[=>\'"]+\s*[\'"]?(\/[^\s\'">,;)]+)' .
|
|
338
|
+
'|(?<![.\w])(?:\/(?:var|home|srv|opt|www|data|sites)\/[^\s\'">,;)]+)/';
|
|
339
|
+
|
|
340
|
+
$results = [];
|
|
341
|
+
|
|
342
|
+
foreach ($deployFiles as $filePath) {
|
|
343
|
+
// Get the most recent commit that touched this file across all branches.
|
|
344
|
+
$logProcess = new Process(
|
|
345
|
+
['git', 'log', '--all', '--full-history', '--format=%H', '-1', '--', $filePath],
|
|
346
|
+
$repoPath,
|
|
347
|
+
null,
|
|
348
|
+
null,
|
|
349
|
+
30
|
|
350
|
+
);
|
|
351
|
+
$logProcess->run();
|
|
352
|
+
$commit = trim($logProcess->getOutput());
|
|
353
|
+
|
|
354
|
+
if (empty($commit)) {
|
|
355
|
+
continue;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// Read the file blob at that commit.
|
|
359
|
+
$showProcess = new Process(
|
|
360
|
+
['git', 'show', "{$commit}:{$filePath}"],
|
|
361
|
+
$repoPath,
|
|
362
|
+
null,
|
|
363
|
+
null,
|
|
364
|
+
30
|
|
365
|
+
);
|
|
366
|
+
$showProcess->run();
|
|
367
|
+
$content = $showProcess->getOutput();
|
|
368
|
+
|
|
369
|
+
if (empty($content)) {
|
|
370
|
+
continue;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// Extract IPs.
|
|
374
|
+
preg_match_all($ipPattern, $content, $ipMatches);
|
|
375
|
+
$ips = array_values(array_unique($ipMatches[0]));
|
|
376
|
+
|
|
377
|
+
// Extract server paths.
|
|
378
|
+
preg_match_all($pathPattern, $content, $pathMatches);
|
|
379
|
+
// Group 1 = named key paths, group 2 = bare unix paths.
|
|
380
|
+
$paths = array_values(array_unique(array_filter(
|
|
381
|
+
array_merge($pathMatches[1], $pathMatches[2])
|
|
382
|
+
)));
|
|
383
|
+
|
|
384
|
+
if (!empty($ips) || !empty($paths)) {
|
|
385
|
+
$results[] = [
|
|
386
|
+
'file' => $filePath,
|
|
387
|
+
'commit' => substr($commit, 0, 12),
|
|
388
|
+
'server_ips' => $ips,
|
|
389
|
+
'server_paths' => $paths,
|
|
390
|
+
];
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
return $results;
|
|
395
|
+
}
|
|
396
|
+
|
|
297
397
|
/**
|
|
298
398
|
* Build the Bash sanitizer script content from the template,
|
|
299
399
|
* injecting the discovered sensitive file list and secret regexes.
|
package/templates/pre-commit
CHANGED
|
@@ -17,7 +17,7 @@ if git diff --cached --name-only | grep -qE "$BLOCKED_PATTERNS"; then
|
|
|
17
17
|
exit 1
|
|
18
18
|
fi
|
|
19
19
|
|
|
20
|
-
if git diff --cached | grep -iE "(DB_PASSWORD
|
|
20
|
+
if git diff --cached | grep -iE "(DB_PASSWORD\s*=\s*\S+|APP_KEY\s*=\s*base64:[A-Za-z0-9+\/=]{40,}|password\s*=\s*['\"][^'\"]{4,}|secret\s*=\s*['\"][^'\"]{4,})"; then
|
|
21
21
|
echo "❌ BLOCKED: possible hardcoded credential detected in diff"
|
|
22
22
|
echo " Use environment variables (.env) instead of inline values"
|
|
23
23
|
exit 1
|