release: rebrand as SquidGate and prepare for open source

- Product name SquidGate (check run, action, package, docs)
- Config path .github/squidgate.yml
- MIT © SquidSec; production README and docs
- CONTRIBUTING, SECURITY, CHANGELOG
- CI verifies dist/ is current
This commit is contained in:
SquidSec Bot
2026-07-28 16:06:15 -04:00
parent 9776fbcfbe
commit a0082ac8f1
20 changed files with 493 additions and 97 deletions
@@ -1,15 +1,12 @@
# .github/security-scan.yml
# Default strict security policy for PRs
# SquidGate — dogfood config for this repository
version: 1
llm:
provider: custom # openai | anthropic | azure | google | custom
provider: custom
model: grok-build-0.1
# For xAI Grok: base url is passed via action input or here if using openai provider with custom endpoint
policy:
block_on: high # critical | high | medium | low | none
block_on: high
min_confidence: medium
categories:
secrets: true
@@ -1,10 +1,10 @@
# .github/security-scan.yml
# Default strict security policy for PRs
# SquidGate config — copy to .github/squidgate.yml
# https://github.com/SquidSec/SquidGate
version: 1
llm:
provider: openai # openai | anthropic | azure | google | custom
model: gpt-4o # or claude-3-5-sonnet-20241022, gemini-1.5-pro, etc.
provider: openai # openai | anthropic | azure | google | custom
model: gpt-4o # or claude-*, gemini-*, grok-*, etc.
policy:
block_on: high # critical | high | medium | low | none
+14 -4
View File
@@ -2,20 +2,30 @@ name: CI
on:
push:
branches: [ main ]
branches: [main]
pull_request:
branches: [ main ]
branches: [main]
permissions:
contents: read
jobs:
test:
runs-on: ubuntu-latest # Change to [self-hosted, linux] or your runner label as needed
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci
- run: npm test
- run: npm run build
- run: npm run package
- name: Ensure dist is up to date
run: |
git diff --exit-code dist/ || {
echo "::error::dist/ is stale. Run npm run build and commit dist/"
exit 1
}
@@ -1,4 +1,4 @@
name: Security Scan
name: SquidGate
on:
pull_request:
@@ -10,21 +10,17 @@ permissions:
checks: write
jobs:
security-scan:
squidgate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Run security-scan
uses: ./ # for local testing; in real use: your-org/security-scan@v1
- name: SquidGate
uses: ./
with:
llm-api-key: ${{ secrets.LLM_API_KEY }}
llm-provider: custom
llm-model: grok-build-0.1
llm-base-url: https://api.x.ai/v1
# github-token: ${{ secrets.GITHUB_TOKEN }} # default is sufficient
# block-on: high
# To relax for testing: block-on: none
+16
View File
@@ -0,0 +1,16 @@
# Changelog
All notable changes to **SquidGate** are documented here.
## [1.0.0] — 2026-07-28
### Added
- Initial public release under MIT as **SquidGate**
- Diff-first LLM PR security gate (GitHub Action)
- Providers: OpenAI, Anthropic, Google, Azure, custom (xAI Grok, Ollama, …)
- Strict default policy (OWASP / CWE-oriented)
- Configurable `block_on`, confidence filter, custom rules
- GitHub Check Run (`SquidGate`) + line annotations + optional PR comment
- Unit tests for config, prompts, parsing, checks
- Docs: configuration, privacy, development
+35
View File
@@ -0,0 +1,35 @@
# Contributing to SquidGate
Thanks for helping. Built by [SquidSec](https://www.SquidOffense.com).
## Quick path
1. Fork + branch from `main`
2. `npm ci && npm test`
3. Make changes; keep modules small and tested
4. `npm run build` (updates `dist/`)
5. Open a PR — SquidGate will scan it
## Guidelines
- **Tests required** for behavior changes
- **No secrets** in commits
- Match existing TypeScript style
- Prefer pure functions in `config` / `prompts` / `llm`
- Update docs when schema or inputs change
## Good first PRs
- Provider adapters
- Better JSON recovery from noisy models
- Optional SARIF upload
- Annotation / PR comment UX
- Docs and monorepo examples
## Security research
See [SECURITY.md](SECURITY.md).
## License
Contributions are licensed under MIT.
+1 -1
View File
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2026 security-scan contributors
Copyright (c) 2026 SquidSec
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
+169 -56
View File
@@ -1,23 +1,44 @@
# security-scan
# SquidGate
LLM-powered GitHub Action that acts as a configurable PR security gate.
[![CI](https://github.com/SquidSec/SquidGate/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/SquidSec/SquidGate/actions/workflows/ci.yml)
[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
[![GitHub Action](https://img.shields.io/badge/GitHub%20Action-ready-2088FF?logo=githubactions&logoColor=white)](action.yml)
[![Node](https://img.shields.io/badge/node-20-green?logo=nodedotjs&logoColor=white)](https://nodejs.org/)
It analyzes the diff of a pull request using an LLM (chosen and paid for by you) and fails the required status check when findings meet or exceed your configured severity threshold.
**LLM-powered PR security gate for GitHub**
*by [SquidSec](https://www.SquidOffense.com)*
- Language-agnostic (any text)
- Diff-first + configurable context
- Strict-by-default, fully tunable
- Supports OpenAI, Anthropic, Google, Azure, custom OpenAI-compatible endpoints
- Creates annotated GitHub Checks + optional PR comment
- Blocks merges via standard "Require status checks"
> One workflow. Every pull request analyzed for security issues.
> Findings above your threshold fail the check and can block merge.
## Quick Start
Language-agnostic. You choose (and pay for) the model — OpenAI, Anthropic, Google, Azure, **Grok / xAI**, Ollama, or any OpenAI-compatible endpoint.
1. Add the workflow:
---
## Why SquidGate?
| | Traditional SAST | SquidGate |
|---|---|---|
| Languages | Per-language rules | Any text-based source |
| Setup | Days of tuning | **~2 minutes** |
| Context | Whole-repo noise | **Diff-first** + context window |
| Model | Fixed engine | **Your** LLM |
| Merge gate | Separate tooling | Native GitHub Check |
| Privacy | Vendor by default | **Only** your endpoint |
Strict by default (OWASP / CWE-oriented). Relax when you mean to.
---
## 60-second setup
### 1. Add the workflow
Create `.github/workflows/squidgate.yml`:
```yaml
# .github/workflows/security-scan.yml
name: Security Scan
name: SquidGate
on:
pull_request:
types: [opened, synchronize, reopened]
@@ -28,89 +49,181 @@ permissions:
checks: write
jobs:
security-scan:
squidgate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: your-org/security-scan@v1
- name: SquidGate
uses: SquidSec/SquidGate@v1
with:
llm-api-key: ${{ secrets.LLM_API_KEY }}
```
2. Store your LLM key as a repository secret `LLM_API_KEY`.
### 2. Add your LLM API key
3. (Recommended) Enable branch protection: **Require status checks** → select `security-scan`.
**Settings → Secrets and variables → Actions → New repository secret**
## Configuration
| Name | Value |
|------|--------|
| `LLM_API_KEY` | Your provider key |
Primary configuration lives in `.github/security-scan.yml` (or override via action inputs).
### 3. Block merges (recommended)
See `.github/security-scan.yml.example` for the full reference.
**Settings → Branches → Branch protection** on `main`:
### Minimal config (strict defaults)
- Require status checks to pass
- Select **`SquidGate`**
Open a PR. Done.
---
## Using Grok (xAI)
```yaml
- uses: SquidSec/SquidGate@v1
with:
llm-api-key: ${{ secrets.LLM_API_KEY }}
llm-provider: custom
llm-model: grok-build-0.1
llm-base-url: https://api.x.ai/v1
```
Key: [console.x.ai](https://console.x.ai/)
---
## Configuration (optional)
Defaults are production-ready. Override with `.github/squidgate.yml`:
```yaml
version: 1
llm:
provider: openai
model: gpt-4o
policy:
block_on: high
block_on: high # critical | high | medium | low | none
min_confidence: medium
categories:
secrets: true
injection: true
# all categories on by default — set false to disable
custom_rules:
- "Never log PII or session tokens"
context:
lines_before: 30
max_files: 50
max_diff_bytes: 500000
output:
comment_on_pr: true
annotate_lines: true
fail_on_error: true
```
### Key settings
Full reference: [docs/configuration.md](docs/configuration.md) · Example: [.github/squidgate.yml.example](.github/squidgate.yml.example)
- `policy.block_on`: `critical` | `high` | `medium` | `low` | `none`
- `llm.provider`: `openai` | `anthropic` | `azure` | `google` | `custom`
- All categories under `policy.categories` are enabled by default.
---
To relax, explicitly set `block_on: medium` or disable categories.
## What it detects
## Action Inputs
- Hardcoded secrets / API keys / private keys
- SQL / command / XSS / SSRF / path traversal
- Insecure deserialization
- Broken authn / authz patterns
- Weak crypto (MD5/SHA1 for security, ECB, hardcoded IVs)
- Dangerous APIs (`eval`, `exec`, `pickle`, `yaml.load`, …)
- Sensitive data in logs / errors
- Patterns aligned with **OWASP Top 10**, **API Security Top 10**, **CWE Top 25**
| Input | Required | Default | Description |
|-----------------|----------|----------------------------|------------------------------------------|
| `github-token` | No | `${{ github.token }}` | Needs `checks:write`, `pull-requests:write` |
| `llm-api-key` | Yes | — | Your LLM provider key (use secrets!) |
| `config-path` | No | `.github/security-scan.yml`| Path to YAML config |
| `llm-provider` | No | from config | Override provider |
| `llm-model` | No | from config | Override model |
| `block-on` | No | from config | Override severity threshold |
| `llm-base-url` | No | — | For custom endpoints / Azure |
---
## Supported Providers & Models (examples)
## Action inputs
- **openai**: `gpt-4o`, `gpt-4o-mini`, `o1-preview` (use models that support JSON mode)
- **anthropic**: `claude-3-5-sonnet-20241022`, `claude-3-opus-20240229`
- **google**: `gemini-1.5-pro`, `gemini-1.5-flash`
- **azure**: Set `llm-provider: azure` + `llm-base-url`
- **custom**: Any OpenAI-compatible server (Ollama, vLLM, LiteLLM, local, etc.)
| Input | Required | Default | Description |
|-------|----------|---------|-------------|
| `llm-api-key` | **Yes** | — | Provider API key |
| `github-token` | No | `${{ github.token }}` | `checks` + `pull-requests` + `contents` |
| `config-path` | No | `.github/squidgate.yml` | Config path |
| `llm-provider` | No | config / `openai` | `openai` \| `anthropic` \| `azure` \| `google` \| `custom` |
| `llm-model` | No | config / `gpt-4o` | Model id |
| `llm-base-url` | No | — | Azure / custom / xAI URL |
| `block-on` | No | config / `high` | Minimum failing severity |
## Outputs
- `findings-count`
- `blocking-findings-count`
- `conclusion`: `success` | `failure`
| Output | Description |
|--------|-------------|
| `findings-count` | Findings after confidence filter |
| `blocking-findings-count` | Findings ≥ `block_on` |
| `conclusion` | `success` \| `failure` |
## Privacy & Security
---
- Your code and diffs are sent **only** to the LLM endpoint you configure.
- No data is stored by this action's maintainers.
- Uses least-privilege permissions.
- Supports private/self-hosted LLMs and self-hosted runners.
## Privacy
- Diffs go **only** to the LLM endpoint **you** configure
- SquidSec does **not** receive or store your code
- Least-privilege permissions; self-hosted runners + private models supported
→ [docs/privacy.md](docs/privacy.md)
---
## How it works
```
PR opened / updated
│
▼
Unified diff (+ context)
│
▼
Policy-aware prompts (temperature 0, JSON)
│
▼
Your LLM
│
▼
Filter → annotate → PR comment
│
▼
Fail check if finding ≥ block_on
```
---
## Development
```bash
npm install
npm run build:ts
npm run package
git clone https://github.com/SquidSec/SquidGate.git
cd SquidGate
npm ci
npm test
npm run build
```
The bundled `dist/index.js` is what the action executes.
See [docs/development.md](docs/development.md) · [CONTRIBUTING.md](CONTRIBUTING.md)
---
## SquidSec
Built by **[SquidSec](https://www.SquidOffense.com)** — security tooling that teams actually ship.
- [SquidOffense.com](https://www.SquidOffense.com)
- [github.com/SquidSec](https://github.com/SquidSec)
- [SquidScanner](https://github.com/DotNetRussell/SquidScanner) — AI attack-surface analysis
---
## License
MIT (or your choice)
[MIT](LICENSE) © SquidSec
+29
View File
@@ -0,0 +1,29 @@
# Security policy
## Supported versions
| Version | Supported |
|---------|-----------|
| `v1.x` | Yes |
| `main` | Best effort |
| Older tags | No |
## Reporting a vulnerability
If you find a security issue in **SquidGate**:
1. **Do not** open a public issue for exploitable bugs.
2. Contact maintainers via [SquidOffense.com](https://www.SquidOffense.com) / SquidSec org.
3. Include: description, impact, repro, suggested fix.
We aim to acknowledge within **72 hours**.
## Scope
**In scope:** secret leakage from the action, unsafe handling of untrusted PR content, supply-chain issues in `dist/`.
**Out of scope:** LLM false negatives/positives (model quality), third-party provider issues.
## Safe harbor
Good-faith research that follows this policy is welcome.
+1 -1
View File
@@ -5,7 +5,7 @@ jest.mock('../src/llm');
describe('action claims via pieces', () => {
it('filters and blocks according to policy (validates core security gate behavior)', () => {
const cfg = loadConfig('.github/security-scan.yml.example');
const cfg = loadConfig('.github/squidgate.yml.example');
const findings = [
{ confidence: 'high', severity: 'high' },
{ confidence: 'medium', severity: 'medium' },
+1 -1
View File
@@ -33,7 +33,7 @@ describe('checks', () => {
expect(result.conclusion).toBe('failure');
expect(result.blockingCount).toBe(1); // only the high one blocks on 'high'
expect(mockCreate).toHaveBeenCalledWith(expect.objectContaining({
name: 'security-scan',
name: 'SquidGate',
head_sha: 'sha123',
conclusion: 'failure',
}));
+1 -1
View File
@@ -2,7 +2,7 @@ import { loadConfig, DEFAULT_CONFIG, shouldBlock, filterFindings } from '../src/
import * as fs from 'fs';
import * as path from 'path';
const tmpConfigPath = path.join(__dirname, 'temp-security-scan.yml');
const tmpConfigPath = path.join(__dirname, 'temp-squidgate.yml');
describe('config', () => {
afterEach(() => {
+7 -7
View File
@@ -1,12 +1,12 @@
name: 'security-scan'
description: 'LLM-powered PR security gate. Analyzes diffs for security issues and blocks merges on high-severity findings.'
author: 'Security Scan Contributors'
name: 'SquidGate'
description: 'LLM-powered PR security gate by SquidSec. Analyzes diffs and blocks merges on high-severity findings.'
author: 'SquidSec'
inputs:
config-path:
description: 'Path to config file'
required: false
default: '.github/security-scan.yml'
default: '.github/squidgate.yml'
llm-provider:
description: 'Override LLM provider (openai | anthropic | azure | google | custom)'
required: false
@@ -21,15 +21,15 @@ inputs:
required: true
default: ${{ github.token }}
llm-api-key:
description: 'LLM provider API key (recommended to use secrets)'
description: 'LLM provider API key (use repository secrets)'
required: true
llm-base-url:
description: 'Optional base URL for custom/OpenAI-compatible or Azure endpoint (advanced)'
description: 'Optional base URL for custom/OpenAI-compatible or Azure endpoint'
required: false
outputs:
findings-count:
description: 'Total number of findings reported by the LLM'
description: 'Total number of findings after confidence filtering'
blocking-findings-count:
description: 'Number of findings that meet or exceed the block_on threshold'
conclusion:
+2 -2
View File
@@ -34852,7 +34852,7 @@ async function createCheckRun(token, owner, repo, headSha, findings, summary, bl
const check = await octokit.rest.checks.create({
owner,
repo,
name: 'security-scan',
name: 'SquidGate',
head_sha: headSha,
status: 'completed',
conclusion: conclusion,
@@ -35187,7 +35187,7 @@ async function run() {
try {
const token = core.getInput('github-token', { required: true });
const apiKey = core.getInput('llm-api-key', { required: true });
const configPath = core.getInput('config-path') || '.github/security-scan.yml';
const configPath = core.getInput('config-path') || '.github/squidgate.yml';
const overrideProvider = core.getInput('llm-provider');
const overrideModel = core.getInput('llm-model');
const overrideBlockOn = core.getInput('block-on');
+92
View File
@@ -0,0 +1,92 @@
# Configuration reference
**SquidGate** primary config: **`.github/squidgate.yml`**
Action inputs override the same fields for one-off runs.
## Full schema
```yaml
version: 1
llm:
provider: openai # openai | anthropic | azure | google | custom
model: gpt-4o
policy:
block_on: high # critical | high | medium | low | none
min_confidence: medium # high | medium | low
categories:
secrets: true
injection: true
authn_authz: true
cryptography: true
insecure_deserialization: true
path_traversal: true
ssrf: true
xss: true
csrf: true
supply_chain: true
hardcoded_credentials: true
dangerous_functions: true
misconfiguration: true
custom_rules: []
context:
lines_before: 30
lines_after: 30
max_files: 50
max_diff_bytes: 500000
output:
comment_on_pr: true
annotate_lines: true
fail_on_error: true
```
## `policy.block_on`
| Value | Behavior |
|-------|----------|
| `critical` | Only critical findings fail |
| `high` | **Default.** high + critical fail |
| `medium` | medium and above fail |
| `low` | almost everything fails |
| `none` | Soft mode — report only |
## `policy.min_confidence`
| Value | Behavior |
|-------|----------|
| `high` | Only clear issues |
| `medium` | **Default** |
| `low` | Include speculative findings |
## Providers
| Provider | `llm-provider` | Notes |
|----------|----------------|-------|
| OpenAI | `openai` | JSON mode; e.g. `gpt-4o` |
| Anthropic | `anthropic` | Claude 3.5+ |
| Google | `google` | Gemini |
| Azure OpenAI | `azure` | Set `llm-base-url` |
| xAI Grok | `custom` | `llm-base-url: https://api.x.ai/v1` |
| Ollama / vLLM / LiteLLM | `custom` | Your server URL |
## Custom rules
```yaml
policy:
custom_rules:
- "Reject MD5 for password hashing"
- "All user-facing HTML must be escaped"
```
## Action input overrides
| Input | Overrides |
|-------|-----------|
| `llm-provider` | `llm.provider` |
| `llm-model` | `llm.model` |
| `llm-base-url` | endpoint (not stored in YAML) |
| `block-on` | `policy.block_on` |
| `config-path` | path to YAML |
+53
View File
@@ -0,0 +1,53 @@
# Development
## Prerequisites
- Node.js 20+
- npm 10+
## Setup
```bash
git clone https://github.com/SquidSec/SquidGate.git
cd SquidGate
npm ci
```
## Scripts
| Command | Purpose |
|---------|---------|
| `npm test` | Unit tests |
| `npm run test:coverage` | Coverage |
| `npm run build:ts` | TypeScript → `lib/` |
| `npm run package` | ncc → `dist/index.js` |
| `npm run build` | compile + bundle |
**Always commit an updated `dist/`** after source changes.
## Layout
```
src/
index.ts # Entry — diff fetch, orchestration
config.ts # YAML, defaults, filtering
prompts.ts # System / user prompts
llm.ts # Providers + JSON extraction
checks.ts # Check Run + PR comments
types.ts
__tests__/
dist/ # Published bundle (committed)
action.yml
```
## Releasing
```bash
npm test && npm run build
git add dist && git commit -m "build: refresh dist"
git tag v1.0.1 && git push origin v1.0.1
# optional major floating tag:
git tag -f v1 && git push -f origin v1
```
Consumers should pin `@v1` or a full SHA.
+45
View File
@@ -0,0 +1,45 @@
# Privacy & security
## What leaves your runner
On each PR, **SquidGate** sends:
1. The **unified diff** (truncated by `max_diff_bytes`)
2. **File paths** and language hints
3. Your **policy configuration**
to the **LLM HTTP endpoint you configure**. Nothing else.
## What SquidSec does *not* do
- No telemetry back to SquidSec
- No code retention by the action maintainers
- No training on your diffs
- No third-party analytics
Your relationship is with **your** model provider. Use enterprise / zero-retention options where available.
## Permissions
```yaml
permissions:
contents: read
pull-requests: write
checks: write
```
## Secrets
Store keys only in GitHub Actions secrets. Never commit API keys.
## Self-hosted & air-gapped
- Self-hosted runners
- `llm-provider: custom` + `llm-base-url` → private model
- Diff never needs to leave your network
## Supply chain
- Pre-built `dist/index.js` (ncc)
- Pin `SquidSec/SquidGate@v1` or a full commit SHA
- CI tests and verifies `dist/` on every push to `main`
+14 -4
View File
@@ -1,7 +1,7 @@
{
"name": "security-scan",
"name": "squidgate",
"version": "1.0.0",
"description": "LLM-powered GitHub Action security gate for pull requests",
"description": "SquidGate — LLM-powered GitHub Action PR security gate by SquidSec",
"main": "dist/index.js",
"scripts": {
"build": "tsc && ncc build lib/index.js -o dist",
@@ -18,10 +18,20 @@
"sast",
"llm",
"pr-check",
"owasp"
"owasp",
"squidsec",
"squidgate"
],
"author": "security-scan contributors",
"author": "SquidSec",
"license": "MIT",
"homepage": "https://github.com/SquidSec/SquidGate",
"repository": {
"type": "git",
"url": "https://github.com/SquidSec/SquidGate.git"
},
"bugs": {
"url": "https://github.com/SquidSec/SquidGate/issues"
},
"type": "commonjs",
"dependencies": {
"@actions/core": "^1.10.1",
+1 -1
View File
@@ -49,7 +49,7 @@ export async function createCheckRun(
const check = await octokit.rest.checks.create({
owner,
repo,
name: 'security-scan',
name: 'SquidGate',
head_sha: headSha,
status: 'completed',
conclusion: conclusion as any,
+1 -1
View File
@@ -122,7 +122,7 @@ async function run(): Promise<void> {
const token = core.getInput('github-token', { required: true });
const apiKey = core.getInput('llm-api-key', { required: true });
const configPath = core.getInput('config-path') || '.github/security-scan.yml';
const configPath = core.getInput('config-path') || '.github/squidgate.yml';
const overrideProvider = core.getInput('llm-provider');
const overrideModel = core.getInput('llm-model');
const overrideBlockOn = core.getInput('block-on') as any;