ai-project-maintainer 0.4.0 → 0.4.1
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/README.md +16 -6
- package/ai-project-maintainer/agents/openai.yaml +6 -6
- package/ai-project-maintainer/references/ci-guardrails.md +55 -55
- package/ai-project-maintainer/references/database.md +60 -60
- package/ai-project-maintainer/references/electron-desktop.md +43 -43
- package/ai-project-maintainer/references/incident-response.md +52 -52
- package/ai-project-maintainer/references/security.md +48 -48
- package/ai-project-maintainer/references/tool-router.md +53 -53
- package/ai-project-maintainer/scripts/bootstrap-local-tools.ps1 +109 -109
- package/ai-project-maintainer/scripts/ci-smoke-gate.mjs +26 -26
- package/ai-project-maintainer/scripts/init-project.mjs +30 -18
- package/ai-project-maintainer/scripts/lib/check-registry.mjs +10 -9
- package/ai-project-maintainer/scripts/lib/checks.mjs +22 -10
- package/ai-project-maintainer/scripts/lib/command-runner.mjs +17 -3
- package/ai-project-maintainer/scripts/lib/policy.mjs +6 -4
- package/ai-project-maintainer/scripts/lib/report.mjs +56 -32
- package/assets/demo-90s-storyboard.svg +98 -0
- package/assets/demo-90s.gif +0 -0
- package/assets/social-preview.png +0 -0
- package/assets/social-preview.svg +55 -0
- package/docs/DEMO.md +68 -61
- package/docs/DEMO.zh-CN.md +75 -69
- package/docs/GITHUB-LAUNCH-CHECKLIST.md +11 -11
- package/docs/POLICY-AND-EXCEPTIONS.zh-CN.md +1 -1
- package/docs/PROMOTION.md +49 -21
- package/docs/SECURITY-WORKFLOW.md +61 -59
- package/docs/UPGRADE-ROADMAP.zh-CN.md +58 -58
- package/docs/demo-output/90-second-demo.html +187 -0
- package/docs/demo-output/before-after-case.md +91 -0
- package/docs/demo-output/security-report.md +62 -61
- package/docs/superpowers/plans/2026-06-29-ci-dogfooding.md +200 -200
- package/examples/demo-ai-app/.ai-maintainer/business-flows.yml +14 -14
- package/examples/demo-ai-app/.ai-maintainer/db-migration-policy.yml +6 -6
- package/examples/demo-ai-app/.ai-maintainer/evidence-sources.yml +18 -18
- package/examples/demo-ai-app/.ai-maintainer/exceptions.yml +1 -1
- package/examples/demo-ai-app/.ai-maintainer/incident-runbook.md +11 -11
- package/examples/demo-ai-app/.ai-maintainer/observability-checklist.yml +7 -7
- package/examples/demo-ai-app/.ai-maintainer/policy.yml +27 -27
- package/examples/demo-ai-app/.ai-maintainer/project-profile.yml +15 -15
- package/examples/demo-ai-app/.ai-maintainer/release-checklist.yml +7 -7
- package/examples/demo-ai-app/.ai-maintainer/risk-policy.yml +5 -5
- package/examples/demo-ai-app/.ai-maintainer/threat-model.md +18 -18
- package/examples/demo-ai-app/README.md +38 -38
- package/examples/demo-ai-app/package-lock.json +15 -15
- package/examples/demo-ai-app/package.json +16 -16
- package/examples/demo-ai-app/scripts/build.mjs +18 -18
- package/examples/demo-ai-app/scripts/create-before-state.mjs +86 -86
- package/examples/demo-ai-app/scripts/run-demo-gate.mjs +95 -95
- package/examples/demo-ai-app/src/order-risk.js +28 -28
- package/examples/demo-ai-app/test/order-risk.test.mjs +24 -24
- package/package.json +2 -1
|
@@ -1,53 +1,53 @@
|
|
|
1
|
-
# Tool Router
|
|
2
|
-
|
|
3
|
-
Use this reference to choose checks dynamically. Run only tools that match the repository and the user's scope.
|
|
4
|
-
|
|
5
|
-
## Selection Order
|
|
6
|
-
|
|
7
|
-
1. Inspect the project with `scripts/probe-project.mjs`.
|
|
8
|
-
2. Use installed Codex skills for security review when available.
|
|
9
|
-
3. Use local CLI tools that are already installed.
|
|
10
|
-
4. Use package-manager or containerized tools only when the repo already uses them or the user approves.
|
|
11
|
-
5. Fall back to manual review with explicit coverage gaps.
|
|
12
|
-
|
|
13
|
-
## Core Routing Table
|
|
14
|
-
|
|
15
|
-
| Surface | Signals | Primary tools | Notes |
|
|
16
|
-
| --- | --- | --- | --- |
|
|
17
|
-
| Code security | Auth, API routes, serializers, file upload, HTTP clients, SQL construction, crypto | `codex-security`, Semgrep, CodeQL, Open Code Review | Prioritize changed files and dataflow into sinks. |
|
|
18
|
-
| Secrets | `.env`, private keys, token-like config, CI variables, committed credentials | Gitleaks, Trivy secret scan | Do not print secret values. Report only location and type. |
|
|
19
|
-
| Dependencies | Lockfiles, Docker images, SBOM, package manifests | Trivy, OSV-Scanner, native package audit | Separate reachable production risk from dev-only noise. |
|
|
20
|
-
| Database migrations | `migrations/`, `db/migrate`, Prisma, Drizzle, Alembic, Flyway, Liquibase, raw SQL | Squawk, Atlas, Bytebase, pgroll, strong_migrations, gh-ost, pt-online-schema-change | Review lock behavior and rollback before correctness polish. |
|
|
21
|
-
| IaC and cloud | Terraform, CloudFormation, Pulumi, Helm, Kubernetes YAML, Docker Compose | Checkov, Trivy config, Conftest/OPA, Kubescape | Focus on public exposure, IAM, network policy, privileged runtime, and secret mounts. |
|
|
22
|
-
| Containers | Dockerfile, compose, image references, deploy manifests | Trivy image/config, Hadolint, Docker build checks | Check root user, privileged mode, writable filesystem, and unpinned base images. |
|
|
23
|
-
| Kubernetes runtime | K8s manifests, `kubectl` context, incident request | k8sgpt, HolmesGPT, Kubescape, Falco, Cilium/Tetragon | Default to read-only commands. |
|
|
24
|
-
| Web/API DAST | Staging URL explicitly provided by user | OWASP ZAP, Nuclei | Require explicit scope for active scans. Never infer internet targets. |
|
|
25
|
-
| Incident response | Alerts, logs, metrics, traces, rollout history, deploys, migrations | HolmesGPT, OpenSRE, k8sgpt, kubectl, observability CLIs/APIs | Build a timeline before proposing fixes. |
|
|
26
|
-
|
|
27
|
-
## Command Patterns
|
|
28
|
-
|
|
29
|
-
Use these as patterns, adapting paths and package managers to the repo.
|
|
30
|
-
|
|
31
|
-
```bash
|
|
32
|
-
semgrep scan --config auto
|
|
33
|
-
trivy fs --scanners vuln,secret,misconfig .
|
|
34
|
-
gitleaks detect --source . --redact
|
|
35
|
-
checkov -d .
|
|
36
|
-
squawk path/to/migration.sql
|
|
37
|
-
atlas migrate lint --dir "file://migrations"
|
|
38
|
-
kubescape scan
|
|
39
|
-
k8sgpt analyze
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
Use CodeQL when a CodeQL database or GitHub code scanning setup already exists, or when the user asks for deeper security analysis.
|
|
43
|
-
|
|
44
|
-
## Missing Tool Handling
|
|
45
|
-
|
|
46
|
-
When a useful tool is unavailable, write a short gap:
|
|
47
|
-
|
|
48
|
-
```text
|
|
49
|
-
Unavailable: squawk. Coverage gap: Postgres migration lock-risk linting was not run.
|
|
50
|
-
Smallest next guardrail: add squawk to CI for changed `.sql` migration files.
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
Do not turn the review into an installation project unless the user asks for setup.
|
|
1
|
+
# Tool Router
|
|
2
|
+
|
|
3
|
+
Use this reference to choose checks dynamically. Run only tools that match the repository and the user's scope.
|
|
4
|
+
|
|
5
|
+
## Selection Order
|
|
6
|
+
|
|
7
|
+
1. Inspect the project with `scripts/probe-project.mjs`.
|
|
8
|
+
2. Use installed Codex skills for security review when available.
|
|
9
|
+
3. Use local CLI tools that are already installed.
|
|
10
|
+
4. Use package-manager or containerized tools only when the repo already uses them or the user approves.
|
|
11
|
+
5. Fall back to manual review with explicit coverage gaps.
|
|
12
|
+
|
|
13
|
+
## Core Routing Table
|
|
14
|
+
|
|
15
|
+
| Surface | Signals | Primary tools | Notes |
|
|
16
|
+
| --- | --- | --- | --- |
|
|
17
|
+
| Code security | Auth, API routes, serializers, file upload, HTTP clients, SQL construction, crypto | `codex-security`, Semgrep, CodeQL, Open Code Review | Prioritize changed files and dataflow into sinks. |
|
|
18
|
+
| Secrets | `.env`, private keys, token-like config, CI variables, committed credentials | Gitleaks, Trivy secret scan | Do not print secret values. Report only location and type. |
|
|
19
|
+
| Dependencies | Lockfiles, Docker images, SBOM, package manifests | Trivy, OSV-Scanner, native package audit | Separate reachable production risk from dev-only noise. |
|
|
20
|
+
| Database migrations | `migrations/`, `db/migrate`, Prisma, Drizzle, Alembic, Flyway, Liquibase, raw SQL | Squawk, Atlas, Bytebase, pgroll, strong_migrations, gh-ost, pt-online-schema-change | Review lock behavior and rollback before correctness polish. |
|
|
21
|
+
| IaC and cloud | Terraform, CloudFormation, Pulumi, Helm, Kubernetes YAML, Docker Compose | Checkov, Trivy config, Conftest/OPA, Kubescape | Focus on public exposure, IAM, network policy, privileged runtime, and secret mounts. |
|
|
22
|
+
| Containers | Dockerfile, compose, image references, deploy manifests | Trivy image/config, Hadolint, Docker build checks | Check root user, privileged mode, writable filesystem, and unpinned base images. |
|
|
23
|
+
| Kubernetes runtime | K8s manifests, `kubectl` context, incident request | k8sgpt, HolmesGPT, Kubescape, Falco, Cilium/Tetragon | Default to read-only commands. |
|
|
24
|
+
| Web/API DAST | Staging URL explicitly provided by user | OWASP ZAP, Nuclei | Require explicit scope for active scans. Never infer internet targets. |
|
|
25
|
+
| Incident response | Alerts, logs, metrics, traces, rollout history, deploys, migrations | HolmesGPT, OpenSRE, k8sgpt, kubectl, observability CLIs/APIs | Build a timeline before proposing fixes. |
|
|
26
|
+
|
|
27
|
+
## Command Patterns
|
|
28
|
+
|
|
29
|
+
Use these as patterns, adapting paths and package managers to the repo.
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
semgrep scan --config auto
|
|
33
|
+
trivy fs --scanners vuln,secret,misconfig .
|
|
34
|
+
gitleaks detect --source . --redact
|
|
35
|
+
checkov -d .
|
|
36
|
+
squawk path/to/migration.sql
|
|
37
|
+
atlas migrate lint --dir "file://migrations"
|
|
38
|
+
kubescape scan
|
|
39
|
+
k8sgpt analyze
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Use CodeQL when a CodeQL database or GitHub code scanning setup already exists, or when the user asks for deeper security analysis.
|
|
43
|
+
|
|
44
|
+
## Missing Tool Handling
|
|
45
|
+
|
|
46
|
+
When a useful tool is unavailable, write a short gap:
|
|
47
|
+
|
|
48
|
+
```text
|
|
49
|
+
Unavailable: squawk. Coverage gap: Postgres migration lock-risk linting was not run.
|
|
50
|
+
Smallest next guardrail: add squawk to CI for changed `.sql` migration files.
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Do not turn the review into an installation project unless the user asks for setup.
|
|
@@ -1,109 +1,109 @@
|
|
|
1
|
-
param(
|
|
2
|
-
[string[]]$Tools = @("gitleaks", "trivy", "squawk"),
|
|
3
|
-
[string]$InstallDir = "$env:USERPROFILE\.codex\security-tools",
|
|
4
|
-
[switch]$AddToPath
|
|
5
|
-
)
|
|
6
|
-
|
|
7
|
-
$ErrorActionPreference = "Stop"
|
|
8
|
-
$binDir = Join-Path $InstallDir "bin"
|
|
9
|
-
New-Item -ItemType Directory -Force -Path $binDir | Out-Null
|
|
10
|
-
$requestedTools = @()
|
|
11
|
-
foreach ($item in $Tools) {
|
|
12
|
-
$requestedTools += ($item -split ',' | ForEach-Object { $_.Trim() } | Where-Object { $_ })
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
function Write-Step($Message) {
|
|
16
|
-
Write-Host "==> $Message"
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function Test-Command($Name) {
|
|
20
|
-
$null -ne (Get-Command $Name -ErrorAction SilentlyContinue)
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function Get-Asset($Repo, [string[]]$Patterns) {
|
|
24
|
-
$release = Invoke-RestMethod -Headers @{ "User-Agent" = "ai-project-maintainer" } -Uri "https://api.github.com/repos/$Repo/releases/latest"
|
|
25
|
-
foreach ($pattern in $Patterns) {
|
|
26
|
-
$asset = $release.assets | Where-Object { $_.name -match $pattern -and $_.name -match '\.zip$' } | Select-Object -First 1
|
|
27
|
-
if ($asset) { return $asset }
|
|
28
|
-
}
|
|
29
|
-
throw "No Windows zip asset matched $($Patterns -join ', ') for $Repo"
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function Install-FromGitHubZip($Name, $Repo, [string[]]$Patterns, $ExeName) {
|
|
33
|
-
if (Test-Command $ExeName) {
|
|
34
|
-
Write-Step "$Name already available on PATH"
|
|
35
|
-
return
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
$asset = Get-Asset -Repo $Repo -Patterns $Patterns
|
|
39
|
-
$tmp = Join-Path ([System.IO.Path]::GetTempPath()) ("$Name-" + [guid]::NewGuid())
|
|
40
|
-
$zip = Join-Path $tmp $asset.name
|
|
41
|
-
New-Item -ItemType Directory -Force -Path $tmp | Out-Null
|
|
42
|
-
|
|
43
|
-
Write-Step "Downloading $Name from $Repo ($($asset.name))"
|
|
44
|
-
Invoke-WebRequest -Headers @{ "User-Agent" = "ai-project-maintainer" } -Uri $asset.browser_download_url -OutFile $zip
|
|
45
|
-
Expand-Archive -LiteralPath $zip -DestinationPath $tmp -Force
|
|
46
|
-
|
|
47
|
-
$exe = Get-ChildItem -Path $tmp -Recurse -File -Filter "$ExeName.exe" | Select-Object -First 1
|
|
48
|
-
if (-not $exe) { throw "Downloaded $Name but could not find $ExeName.exe in archive" }
|
|
49
|
-
|
|
50
|
-
Copy-Item -LiteralPath $exe.FullName -Destination (Join-Path $binDir "$ExeName.exe") -Force
|
|
51
|
-
Write-Step "Installed $Name to $binDir"
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
function Install-WithUvTool($Name, $Package, $ExeName) {
|
|
55
|
-
if (Test-Command $ExeName) {
|
|
56
|
-
Write-Step "$Name already available on PATH"
|
|
57
|
-
return
|
|
58
|
-
}
|
|
59
|
-
if (-not (Test-Command "uv")) {
|
|
60
|
-
Write-Warning "uv is not installed; cannot install $Name locally. Install uv, Python, or Docker and rerun."
|
|
61
|
-
return
|
|
62
|
-
}
|
|
63
|
-
Write-Step "Installing $Name with uv tool install $Package"
|
|
64
|
-
& uv tool install $Package
|
|
65
|
-
if ($LASTEXITCODE -ne 0) {
|
|
66
|
-
throw "uv tool install $Package failed with exit code $LASTEXITCODE"
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
foreach ($tool in $requestedTools) {
|
|
71
|
-
try {
|
|
72
|
-
switch ($tool.ToLowerInvariant()) {
|
|
73
|
-
"gitleaks" {
|
|
74
|
-
Install-FromGitHubZip -Name "gitleaks" -Repo "gitleaks/gitleaks" -Patterns @("windows.*x64", "windows.*64") -ExeName "gitleaks"
|
|
75
|
-
}
|
|
76
|
-
"trivy" {
|
|
77
|
-
Install-FromGitHubZip -Name "trivy" -Repo "aquasecurity/trivy" -Patterns @("windows-64bit", "windows.*64") -ExeName "trivy"
|
|
78
|
-
}
|
|
79
|
-
"squawk" {
|
|
80
|
-
Install-FromGitHubZip -Name "squawk" -Repo "sbdchd/squawk" -Patterns @("windows.*x86_64", "windows.*amd64", "windows.*64") -ExeName "squawk"
|
|
81
|
-
}
|
|
82
|
-
"semgrep" {
|
|
83
|
-
Install-WithUvTool -Name "semgrep" -Package "semgrep" -ExeName "semgrep"
|
|
84
|
-
}
|
|
85
|
-
"checkov" {
|
|
86
|
-
Install-WithUvTool -Name "checkov" -Package "checkov" -ExeName "checkov"
|
|
87
|
-
}
|
|
88
|
-
default {
|
|
89
|
-
Write-Warning "Unknown local bootstrap tool '$tool'. Supported: gitleaks, trivy, squawk, semgrep, checkov."
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
} catch {
|
|
93
|
-
Write-Warning "Could not install ${tool}: $($_.Exception.Message)"
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
if ($AddToPath) {
|
|
98
|
-
$current = [Environment]::GetEnvironmentVariable("Path", "User")
|
|
99
|
-
$parts = $current -split ';' | Where-Object { $_ }
|
|
100
|
-
if ($parts -notcontains $binDir) {
|
|
101
|
-
[Environment]::SetEnvironmentVariable("Path", ($parts + $binDir -join ';'), "User")
|
|
102
|
-
Write-Step "Added $binDir to the user PATH. Restart terminals/Codex to pick it up."
|
|
103
|
-
} else {
|
|
104
|
-
Write-Step "$binDir is already in the user PATH"
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
Write-Step "Installed tools:"
|
|
109
|
-
Get-ChildItem -File -LiteralPath $binDir | Select-Object Name,Length,LastWriteTime
|
|
1
|
+
param(
|
|
2
|
+
[string[]]$Tools = @("gitleaks", "trivy", "squawk"),
|
|
3
|
+
[string]$InstallDir = "$env:USERPROFILE\.codex\security-tools",
|
|
4
|
+
[switch]$AddToPath
|
|
5
|
+
)
|
|
6
|
+
|
|
7
|
+
$ErrorActionPreference = "Stop"
|
|
8
|
+
$binDir = Join-Path $InstallDir "bin"
|
|
9
|
+
New-Item -ItemType Directory -Force -Path $binDir | Out-Null
|
|
10
|
+
$requestedTools = @()
|
|
11
|
+
foreach ($item in $Tools) {
|
|
12
|
+
$requestedTools += ($item -split ',' | ForEach-Object { $_.Trim() } | Where-Object { $_ })
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function Write-Step($Message) {
|
|
16
|
+
Write-Host "==> $Message"
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function Test-Command($Name) {
|
|
20
|
+
$null -ne (Get-Command $Name -ErrorAction SilentlyContinue)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function Get-Asset($Repo, [string[]]$Patterns) {
|
|
24
|
+
$release = Invoke-RestMethod -Headers @{ "User-Agent" = "ai-project-maintainer" } -Uri "https://api.github.com/repos/$Repo/releases/latest"
|
|
25
|
+
foreach ($pattern in $Patterns) {
|
|
26
|
+
$asset = $release.assets | Where-Object { $_.name -match $pattern -and $_.name -match '\.zip$' } | Select-Object -First 1
|
|
27
|
+
if ($asset) { return $asset }
|
|
28
|
+
}
|
|
29
|
+
throw "No Windows zip asset matched $($Patterns -join ', ') for $Repo"
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function Install-FromGitHubZip($Name, $Repo, [string[]]$Patterns, $ExeName) {
|
|
33
|
+
if (Test-Command $ExeName) {
|
|
34
|
+
Write-Step "$Name already available on PATH"
|
|
35
|
+
return
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
$asset = Get-Asset -Repo $Repo -Patterns $Patterns
|
|
39
|
+
$tmp = Join-Path ([System.IO.Path]::GetTempPath()) ("$Name-" + [guid]::NewGuid())
|
|
40
|
+
$zip = Join-Path $tmp $asset.name
|
|
41
|
+
New-Item -ItemType Directory -Force -Path $tmp | Out-Null
|
|
42
|
+
|
|
43
|
+
Write-Step "Downloading $Name from $Repo ($($asset.name))"
|
|
44
|
+
Invoke-WebRequest -Headers @{ "User-Agent" = "ai-project-maintainer" } -Uri $asset.browser_download_url -OutFile $zip
|
|
45
|
+
Expand-Archive -LiteralPath $zip -DestinationPath $tmp -Force
|
|
46
|
+
|
|
47
|
+
$exe = Get-ChildItem -Path $tmp -Recurse -File -Filter "$ExeName.exe" | Select-Object -First 1
|
|
48
|
+
if (-not $exe) { throw "Downloaded $Name but could not find $ExeName.exe in archive" }
|
|
49
|
+
|
|
50
|
+
Copy-Item -LiteralPath $exe.FullName -Destination (Join-Path $binDir "$ExeName.exe") -Force
|
|
51
|
+
Write-Step "Installed $Name to $binDir"
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function Install-WithUvTool($Name, $Package, $ExeName) {
|
|
55
|
+
if (Test-Command $ExeName) {
|
|
56
|
+
Write-Step "$Name already available on PATH"
|
|
57
|
+
return
|
|
58
|
+
}
|
|
59
|
+
if (-not (Test-Command "uv")) {
|
|
60
|
+
Write-Warning "uv is not installed; cannot install $Name locally. Install uv, Python, or Docker and rerun."
|
|
61
|
+
return
|
|
62
|
+
}
|
|
63
|
+
Write-Step "Installing $Name with uv tool install $Package"
|
|
64
|
+
& uv tool install $Package
|
|
65
|
+
if ($LASTEXITCODE -ne 0) {
|
|
66
|
+
throw "uv tool install $Package failed with exit code $LASTEXITCODE"
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
foreach ($tool in $requestedTools) {
|
|
71
|
+
try {
|
|
72
|
+
switch ($tool.ToLowerInvariant()) {
|
|
73
|
+
"gitleaks" {
|
|
74
|
+
Install-FromGitHubZip -Name "gitleaks" -Repo "gitleaks/gitleaks" -Patterns @("windows.*x64", "windows.*64") -ExeName "gitleaks"
|
|
75
|
+
}
|
|
76
|
+
"trivy" {
|
|
77
|
+
Install-FromGitHubZip -Name "trivy" -Repo "aquasecurity/trivy" -Patterns @("windows-64bit", "windows.*64") -ExeName "trivy"
|
|
78
|
+
}
|
|
79
|
+
"squawk" {
|
|
80
|
+
Install-FromGitHubZip -Name "squawk" -Repo "sbdchd/squawk" -Patterns @("windows.*x86_64", "windows.*amd64", "windows.*64") -ExeName "squawk"
|
|
81
|
+
}
|
|
82
|
+
"semgrep" {
|
|
83
|
+
Install-WithUvTool -Name "semgrep" -Package "semgrep" -ExeName "semgrep"
|
|
84
|
+
}
|
|
85
|
+
"checkov" {
|
|
86
|
+
Install-WithUvTool -Name "checkov" -Package "checkov" -ExeName "checkov"
|
|
87
|
+
}
|
|
88
|
+
default {
|
|
89
|
+
Write-Warning "Unknown local bootstrap tool '$tool'. Supported: gitleaks, trivy, squawk, semgrep, checkov."
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
} catch {
|
|
93
|
+
Write-Warning "Could not install ${tool}: $($_.Exception.Message)"
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if ($AddToPath) {
|
|
98
|
+
$current = [Environment]::GetEnvironmentVariable("Path", "User")
|
|
99
|
+
$parts = $current -split ';' | Where-Object { $_ }
|
|
100
|
+
if ($parts -notcontains $binDir) {
|
|
101
|
+
[Environment]::SetEnvironmentVariable("Path", ($parts + $binDir -join ';'), "User")
|
|
102
|
+
Write-Step "Added $binDir to the user PATH. Restart terminals/Codex to pick it up."
|
|
103
|
+
} else {
|
|
104
|
+
Write-Step "$binDir is already in the user PATH"
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
Write-Step "Installed tools:"
|
|
109
|
+
Get-ChildItem -File -LiteralPath $binDir | Select-Object Name,Length,LastWriteTime
|
|
@@ -1,26 +1,26 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { runLocalGate } from "./run-local-gate.mjs";
|
|
4
|
-
|
|
5
|
-
const projectRoot = path.resolve(process.argv[2] || process.cwd());
|
|
6
|
-
const outputPath = process.argv[3] || "reports/security-report.json";
|
|
7
|
-
|
|
8
|
-
const report = runLocalGate(projectRoot, {
|
|
9
|
-
noTests: true,
|
|
10
|
-
outputPath,
|
|
11
|
-
writeReports: true,
|
|
12
|
-
runnerOptions: {
|
|
13
|
-
envPath: "",
|
|
14
|
-
},
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
const summary = {
|
|
18
|
-
status: report.passed ? "PASS" : "FAIL",
|
|
19
|
-
blockers: report.summary?.blockers ?? 0,
|
|
20
|
-
warnings: report.summary?.warnings ?? 0,
|
|
21
|
-
coverageGaps: report.summary?.coverageGaps ?? 0,
|
|
22
|
-
outputPath,
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
console.log(JSON.stringify(summary, null, 2));
|
|
26
|
-
process.exit(report.passed ? 0 : 1);
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { runLocalGate } from "./run-local-gate.mjs";
|
|
4
|
+
|
|
5
|
+
const projectRoot = path.resolve(process.argv[2] || process.cwd());
|
|
6
|
+
const outputPath = process.argv[3] || "reports/security-report.json";
|
|
7
|
+
|
|
8
|
+
const report = runLocalGate(projectRoot, {
|
|
9
|
+
noTests: true,
|
|
10
|
+
outputPath,
|
|
11
|
+
writeReports: true,
|
|
12
|
+
runnerOptions: {
|
|
13
|
+
envPath: "",
|
|
14
|
+
},
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
const summary = {
|
|
18
|
+
status: report.passed ? "PASS" : "FAIL",
|
|
19
|
+
blockers: report.summary?.blockers ?? 0,
|
|
20
|
+
warnings: report.summary?.warnings ?? 0,
|
|
21
|
+
coverageGaps: report.summary?.coverageGaps ?? 0,
|
|
22
|
+
outputPath,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
console.log(JSON.stringify(summary, null, 2));
|
|
26
|
+
process.exit(report.passed ? 0 : 1);
|
|
@@ -34,7 +34,7 @@ warn_on:
|
|
|
34
34
|
|
|
35
35
|
const exceptionsTemplate = `exceptions:
|
|
36
36
|
- id: "example-dev-only-vuln"
|
|
37
|
-
check: "
|
|
37
|
+
check: "package-audit"
|
|
38
38
|
reason: "dev-only transitive dependency, not shipped"
|
|
39
39
|
expires: "2026-09-01"
|
|
40
40
|
owner: "repo-owner"
|
|
@@ -91,20 +91,32 @@ jobs:
|
|
|
91
91
|
npm install
|
|
92
92
|
fi
|
|
93
93
|
|
|
94
|
-
- name: Install security scanners
|
|
95
|
-
shell: bash
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
94
|
+
- name: Install security scanners
|
|
95
|
+
shell: bash
|
|
96
|
+
env:
|
|
97
|
+
GITLEAKS_VERSION: v8.30.0
|
|
98
|
+
TRIVY_VERSION: v0.71.2
|
|
99
|
+
SEMGREP_VERSION: 1.168.0
|
|
100
|
+
ZIZMOR_VERSION: 1.26.1
|
|
101
|
+
OSV_SCANNER_VERSION: v2.4.0
|
|
102
|
+
ACTIONLINT_VERSION: v1.7.12
|
|
103
|
+
SYFT_VERSION: v1.46.0
|
|
104
|
+
GRYPE_VERSION: v0.111.1
|
|
105
|
+
CHECKOV_VERSION: 3.3.2
|
|
106
|
+
SCORECARD_VERSION: v5.3.0
|
|
107
|
+
run: |
|
|
108
|
+
set -euo pipefail
|
|
109
|
+
mkdir -p "$HOME/.local/bin"
|
|
110
|
+
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
|
|
111
|
+
echo "$HOME/go/bin" >> "$GITHUB_PATH"
|
|
112
|
+
python -m pip install --user "semgrep==$SEMGREP_VERSION" "zizmor==$ZIZMOR_VERSION" "checkov==$CHECKOV_VERSION"
|
|
113
|
+
go install "github.com/zricethezav/gitleaks/v8@$GITLEAKS_VERSION"
|
|
114
|
+
go install "github.com/google/osv-scanner/v2/cmd/osv-scanner@$OSV_SCANNER_VERSION"
|
|
115
|
+
go install "github.com/rhysd/actionlint/cmd/actionlint@$ACTIONLINT_VERSION"
|
|
116
|
+
go install "github.com/anchore/syft/cmd/syft@$SYFT_VERSION"
|
|
117
|
+
go install "github.com/anchore/grype/cmd/grype@$GRYPE_VERSION"
|
|
118
|
+
go install "github.com/ossf/scorecard/v5@$SCORECARD_VERSION"
|
|
119
|
+
curl -sSfL "https://raw.githubusercontent.com/aquasecurity/trivy/$TRIVY_VERSION/contrib/install.sh" | sh -s -- -b "$HOME/.local/bin" "$TRIVY_VERSION"
|
|
108
120
|
|
|
109
121
|
- name: Checkout AI Project Maintainer
|
|
110
122
|
shell: bash
|
|
@@ -132,9 +144,9 @@ jobs:
|
|
|
132
144
|
cat reports/security-report.md >> "$GITHUB_STEP_SUMMARY"
|
|
133
145
|
fi
|
|
134
146
|
|
|
135
|
-
- name: Upload SARIF to code scanning
|
|
136
|
-
if: always()
|
|
137
|
-
uses: github/codeql-action/upload-sarif@v4
|
|
147
|
+
- name: Upload SARIF to code scanning
|
|
148
|
+
if: always()
|
|
149
|
+
uses: github/codeql-action/upload-sarif@v4
|
|
138
150
|
with:
|
|
139
151
|
sarif_file: reports/security-report.sarif
|
|
140
152
|
continue-on-error: true
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
runDatabaseChecks,
|
|
4
|
-
runElectronChecks,
|
|
1
|
+
import {
|
|
2
|
+
runActionlintChecks,
|
|
3
|
+
runDatabaseChecks,
|
|
4
|
+
runElectronChecks,
|
|
5
5
|
runGrypeChecks,
|
|
6
6
|
runIacChecks,
|
|
7
7
|
runMegaLinterChecks,
|
|
@@ -12,9 +12,10 @@ import {
|
|
|
12
12
|
runScorecardChecks,
|
|
13
13
|
runSecretChecks,
|
|
14
14
|
runSyftChecks,
|
|
15
|
-
runTestChecks,
|
|
16
|
-
runTrivyFilesystemChecks,
|
|
17
|
-
|
|
15
|
+
runTestChecks,
|
|
16
|
+
runTrivyFilesystemChecks,
|
|
17
|
+
runZizmorChecks,
|
|
18
|
+
} from "./checks.mjs";
|
|
18
19
|
|
|
19
20
|
const builtinCheckRegistry = [
|
|
20
21
|
{ id: "tests", group: "tests", title: "Project tests and release scripts", requiredTools: ["npm", "pnpm", "yarn", "bun"], detect: (project) => Boolean(project.packageJson), run: ({ project, options }) => runTestChecks(project, options), defaultLevel: "block" },
|
|
@@ -25,8 +26,8 @@ const builtinCheckRegistry = [
|
|
|
25
26
|
{ id: "semgrep", group: "sast", title: "Semgrep static scan", requiredTools: ["semgrep"], detect: () => true, run: ({ project, options }) => runSastChecks(project, options), defaultLevel: "block" },
|
|
26
27
|
{ id: "syft", group: "supply-chain", title: "Syft SBOM", requiredTools: ["syft"], detect: () => true, run: ({ project, options }) => runSyftChecks(project, options), defaultLevel: "warn" },
|
|
27
28
|
{ id: "grype", group: "supply-chain", title: "Grype vulnerability scan", requiredTools: ["grype"], detect: () => true, run: ({ project, options }) => runGrypeChecks(project, options), defaultLevel: "warn" },
|
|
28
|
-
{ id: "actionlint", group: "ci-security", title: "actionlint workflow lint", requiredTools: ["actionlint"], detect: (project) => (project.riskSurfaces?.ci || []).length > 0, run: ({ project, options }) =>
|
|
29
|
-
{ id: "zizmor", group: "ci-security", title: "zizmor workflow security", requiredTools: ["zizmor"], detect: (project) => (project.riskSurfaces?.ci || []).length > 0, run: ({ project, options }) =>
|
|
29
|
+
{ id: "actionlint", group: "ci-security", title: "actionlint workflow lint", requiredTools: ["actionlint"], detect: (project) => (project.riskSurfaces?.ci || []).length > 0, run: ({ project, options }) => runActionlintChecks(project, options), defaultLevel: "block" },
|
|
30
|
+
{ id: "zizmor", group: "ci-security", title: "zizmor workflow security", requiredTools: ["zizmor"], detect: (project) => (project.riskSurfaces?.ci || []).length > 0, run: ({ project, options }) => runZizmorChecks(project, options), defaultLevel: "warn" },
|
|
30
31
|
{ id: "checkov", group: "iac", title: "Checkov IaC scan", requiredTools: ["checkov"], detect: (project) => (project.riskSurfaces?.infra || []).length > 0, run: ({ project, options }) => runIacChecks(project, options).filter((check) => check.checkId === "checkov" || check.checkId === "trivy-config"), defaultLevel: "warn" },
|
|
31
32
|
{ id: "electron", group: "electron", title: "Electron baseline", requiredTools: [], detect: (project) => Boolean(project.electron?.detected), run: ({ project }) => runElectronChecks(project), defaultLevel: "block" },
|
|
32
33
|
{ id: "database", group: "database", title: "Database migration review", requiredTools: ["squawk"], detect: (project) => (project.riskSurfaces?.database || []).length > 0, run: ({ project, options }) => runDatabaseChecks(project, options), defaultLevel: "block" },
|
|
@@ -191,16 +191,28 @@ export function runGrypeChecks(project, options = {}) {
|
|
|
191
191
|
];
|
|
192
192
|
}
|
|
193
193
|
|
|
194
|
-
export function runCiSecurityChecks(project, options = {}) {
|
|
195
|
-
if (!(project.riskSurfaces?.ci || []).length) return [];
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
];
|
|
203
|
-
|
|
194
|
+
export function runCiSecurityChecks(project, options = {}) {
|
|
195
|
+
if (!(project.riskSurfaces?.ci || []).length) return [];
|
|
196
|
+
return [...runActionlintChecks(project, options), ...runZizmorChecks(project, options)];
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export function runActionlintChecks(project, options = {}) {
|
|
200
|
+
if (!(project.riskSurfaces?.ci || []).length) return [];
|
|
201
|
+
const runner = options.runnerOptions || {};
|
|
202
|
+
const actionlint = runCommand("actionlint", [], { ...runner, cwd: project.root, timeoutMs: 5 * 60 * 1000 });
|
|
203
|
+
return [
|
|
204
|
+
makeCheck("actionlint workflow lint", "ci-security", actionlint, actionlint.status === "fail", "GitHub Actions workflow syntax and common mistakes must be fixed.", { checkId: "actionlint" }),
|
|
205
|
+
];
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export function runZizmorChecks(project, options = {}) {
|
|
209
|
+
if (!(project.riskSurfaces?.ci || []).length) return [];
|
|
210
|
+
const runner = options.runnerOptions || {};
|
|
211
|
+
const zizmor = runCommand("zizmor", [".github/workflows"], { ...runner, cwd: project.root, timeoutMs: 5 * 60 * 1000 });
|
|
212
|
+
return [
|
|
213
|
+
makeCheck("zizmor workflow security", "ci-security", zizmor, zizmor.status === "fail", "High-risk GitHub Actions patterns must be fixed.", { checkId: "zizmor" }),
|
|
214
|
+
];
|
|
215
|
+
}
|
|
204
216
|
|
|
205
217
|
export function runIacChecks(project, options = {}) {
|
|
206
218
|
if (!(project.riskSurfaces?.infra || []).length) return [];
|
|
@@ -78,9 +78,23 @@ function spawnTarget(resolved, args) {
|
|
|
78
78
|
};
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
export function runCommand(command, commandArgs = [], options = {}) {
|
|
82
|
-
|
|
83
|
-
|
|
81
|
+
export function runCommand(command, commandArgs = [], options = {}) {
|
|
82
|
+
if (typeof options.commandRunner === "function") {
|
|
83
|
+
const started = Date.now();
|
|
84
|
+
const commandText = [command, ...commandArgs].join(" ");
|
|
85
|
+
const result = options.commandRunner(command, commandArgs, options) || {};
|
|
86
|
+
return {
|
|
87
|
+
status: result.status || (result.code === 0 ? "pass" : "fail"),
|
|
88
|
+
command: result.command || commandText,
|
|
89
|
+
stdout: tail(result.stdout),
|
|
90
|
+
stderr: tail(result.stderr || result.error?.message || ""),
|
|
91
|
+
code: result.code ?? null,
|
|
92
|
+
durationMs: result.durationMs ?? Date.now() - started,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const resolved = commandPath(command, options);
|
|
97
|
+
const commandText = [command, ...commandArgs].join(" ");
|
|
84
98
|
if (!resolved) {
|
|
85
99
|
return {
|
|
86
100
|
status: "missing",
|
|
@@ -101,10 +101,12 @@ export function validateExceptions(exceptions, now = new Date()) {
|
|
|
101
101
|
return { valid, invalid };
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
-
function matchesException(check, exception) {
|
|
105
|
-
const target = String(exception.check || "").toLowerCase();
|
|
106
|
-
return
|
|
107
|
-
|
|
104
|
+
function matchesException(check, exception) {
|
|
105
|
+
const target = String(exception.check || "").toLowerCase();
|
|
106
|
+
return [check.checkId, check.name, check.group]
|
|
107
|
+
.map((value) => String(value || "").toLowerCase())
|
|
108
|
+
.includes(target);
|
|
109
|
+
}
|
|
108
110
|
|
|
109
111
|
function policyKeyForCheck(check) {
|
|
110
112
|
if (check.group === "tests") return "tests";
|