Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,8 @@ tracing = "0.1.44"
tracing-subscriber = { version = "0.3.22", features = ["env-filter"] }
tree-sitter = "0.26.9"
tree-sitter-c = "0.24.2"
hmac = "0.13"
base64 = "0.22"
subtle = "2"
url = "2.5.8"
percent-encoding = "2.3.2"
14 changes: 13 additions & 1 deletion Settings.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,21 @@ description = ""
[forge]
enabled = false
# provider = "github"
# webhook_secret = "your-webhook-secret"
# api_token = "your-api-token"

# Webhook authentication (recommended for production).
# When configured, Sashiko authenticates incoming webhook requests using
# this token. Non-localhost requests are permitted without
# --enable-unsafe-all-submit when a secret is set.
#
# For GitLab 19.0+ signing token (recommended):
# webhook_secret = "whsec_YOUR_BASE64_TOKEN"
# For GitHub or plain secret token:
# webhook_secret = "YOUR_SECRET"
# Generate a strong secret: openssl rand -hex 32
# See docs/WEBHOOK_SECURITY.md for full setup guide.
# webhook_secret = ""

[subsystems]
# Regex map: email address pattern -> subsystem name
# mapping = [
Expand Down
149 changes: 149 additions & 0 deletions designs/DESIGN_WEBHOOK_INPUT_HARDENING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
# Design: Webhook & Ingestion Input Hardening

## Goal
Harden all HTTP ingestion endpoints against untrusted input. Forge
webhook and submit API endpoints accept external data that flows into
git commands, database queries, and the review pipeline. This design
addresses four related security gaps that share the same attack
surface.

## Threat Model

When Sashiko accepts unauthenticated webhook requests, an attacker
who can reach the endpoint can:

1. Forge webhooks to trigger expensive AI reviews (cost amplification)
2. Inject arbitrary repository URLs for Sashiko to clone (SSRF risk)
3. Exhaust server memory via oversized payloads or gzip bombs
4. Pass malformed commit SHAs to git commands
5. Pollute the review database with fake patchsets

## Architecture Changes

### 1. Webhook Signature Verification (`src/forge.rs`)

The `ForgeProvider` trait gains body and secret parameters:

```rust
fn validate_event(
&self,
headers: &HeaderMap,
body: &Bytes,
secret: Option<&str>,
) -> Result<(), StatusCode>;
```

Three verification methods, selected by header presence:

- **Standard Webhooks HMAC-SHA256** (GitLab 19.0+ signing token):
verifies `webhook-signature` header over
`{webhook-id}.{webhook-timestamp}.{body}`.
- **GitHub HMAC-SHA256**: verifies `X-Hub-Signature-256` header.
- **Legacy secret token**: constant-time comparison of
`X-Gitlab-Token` header.

Secret type auto-detection: `whsec_` prefix indicates a Standard
Webhooks signing token (base64-decoded); plain strings are used as
raw key bytes. A warning is logged if base64 decoding fails for a
`whsec_`-prefixed token.

All comparisons use the `subtle` crate for constant-time operations.

### 2. Access Control Revision (`src/api.rs`)

When `webhook_secret` is configured, non-localhost requests are
permitted because the signature check is the access control. The
`--enable-unsafe-all-submit` flag is only needed for unauthenticated
setups.

```rust
let is_loopback = addr.ip().to_canonical().is_loopback();
let has_secret = webhook_secret.is_some();
if !is_loopback && !has_secret && !state.allow_all_submit {
return Err(StatusCode::FORBIDDEN);
}
```

### 3. Body Size Limits (`src/api.rs`)

- Explicit `DefaultBodyLimit::max(25 MiB)` on the axum router.
Sized to accommodate large CLI mbox submissions and GitHub
webhook payloads (up to 25 MB).
- HTTP download cap (10 MiB) on `fetch_and_inject_thread` for
lore.kernel.org mbox fetches.
- Decompression cap (50 MiB) via `Read::take()` on the gzip decoder
to prevent decompression bombs.

### 4. Input Validation (`src/forge.rs`)

Validation functions applied in both forge `parse_payload` methods:

- `is_valid_git_sha(s)`: accepts 40-char SHA-1 or 64-char SHA-256,
hex digits only.
- `is_safe_repo_url(url)`: requires `https://`, `http://`, or `git@`
scheme; rejects known SSRF targets (cloud metadata endpoints,
loopback addresses).
- `pr_number > 0` check on both forge providers.

These validations are applied only in the forge webhook path, not in
the CLI submit path, because the CLI legitimately sends git refs and
local filesystem paths.

Message-ID path separator sanitization (`/` and `\`) is applied in
the Thread submit handler to prevent URL path manipulation when
constructing lore.kernel.org fetch URLs.

### 5. Startup Warnings (`src/main.rs`)

Two warning conditions at startup:

- Forge enabled without `webhook_secret` and without
`--enable-unsafe-all-submit`: warns that non-localhost requests
will be rejected.
- Forge enabled without `webhook_secret` but with
`--enable-unsafe-all-submit`: warns about accepting unauthenticated
requests.

## Configuration

```toml
[forge]
enabled = true
provider = "gitlab"
webhook_secret = "whsec_..." # signing token (recommended)
# OR
webhook_secret = "my-secret" # plain secret token
```

No new required config fields. The existing `webhook_secret` field
(previously dead code) is activated.

## Dependencies

New crate dependencies:
- `hmac = "0.13"` (HMAC-SHA256 computation)
- `base64 = "0.22"` (signing token key decoding)
- `subtle = "2"` (constant-time comparison)

## Deployment Topologies

The design supports four deployment patterns, documented in
`docs/WEBHOOK_SECURITY.md`:

1. Public server with reverse proxy (nginx/Caddy terminates TLS)
2. Tunnel-based (ngrok, Cloudflare Tunnel, SSH)
3. Self-hosted forge on same LAN
4. Script-based polling (cronjob + curl)

When `webhook_secret` is configured, topologies 1-3 do not require
`--enable-unsafe-all-submit`.

## Risks

- SSRF blocklist is best-effort (DNS rebinding can bypass string
checks). The primary access control is signature verification.
- `subtle::ConstantTimeEq` reveals length differences between
compared strings (acceptable for webhook secrets with sufficient
entropy).
- No replay attack prevention via timestamp validation in this
version (documented as future enhancement).
79 changes: 11 additions & 68 deletions docs/FORGE_SETUP.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,11 +136,12 @@ host = "127.0.0.1" # Listen address
port = 8080 # Port for webhook endpoint
```

**Security considerations:**
- By default, Sashiko only accepts webhooks from localhost
- For production, use `--enable-unsafe-all-submit` flag (behind firewall/proxy)
- Always use HTTPS in production with valid certificates
- Implement webhook signature validation when available
**Security:** Sashiko supports webhook signature verification via HMAC-SHA256
(GitHub and GitLab 19.0+ signing tokens) and legacy secret tokens. When
`webhook_secret` is configured, non-localhost requests are authenticated
without requiring `--enable-unsafe-all-submit`. See the
[Webhook Security Guide](WEBHOOK_SECURITY.md) for setup instructions and
production deployment recommendations.

### Git Configuration

Expand Down Expand Up @@ -345,67 +346,9 @@ api_endpoint = "https://api.yourforge.com"

## Security Best Practices

### Production Deployment

1. **Use HTTPS with valid certificates**
- Set up reverse proxy (nginx, Apache, Caddy)
- Obtain SSL/TLS certificates (Let's Encrypt)
- Terminate TLS at proxy, forward to Sashiko

2. **Implement authentication**
- Use webhook secrets when available
- Validate webhook signatures
- Restrict by source IP (if forge IPs are known)

3. **Network isolation**
- Run Sashiko on private network
- Use VPN or SSH tunneling for access
- Firewall rules to limit exposure

4. **Rate limiting**
- Configure at reverse proxy level
- Prevent abuse and DoS attempts
- Monitor webhook delivery rates

5. **Audit logging**
- Log all webhook deliveries
- Track review queue metrics
- Monitor for unusual patterns

### Example Nginx Configuration

```nginx
server {
listen 443 ssl http2;
server_name sashiko.example.com;

ssl_certificate /etc/letsencrypt/live/sashiko.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/sashiko.example.com/privkey.pem;

# Security headers
add_header Strict-Transport-Security "max-age=31536000" always;
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;

# Rate limiting
limit_req_zone $binary_remote_addr zone=webhook:10m rate=10r/m;
limit_req zone=webhook burst=5;

location /api/webhook/ {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}

location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
```
Sashiko supports webhook signature verification. For complete setup
instructions, deployment topologies, reverse proxy examples, and a
production checklist, see the [Webhook Security Guide](WEBHOOK_SECURITY.md).

## Forge Comparison

Expand All @@ -414,8 +357,8 @@ server {
| Webhook delivery | ✅ | ✅ | **Required** |
| JSON payloads | ✅ | ✅ | **Required** |
| Event filtering | ✅ | ✅ | **Required** |
| Webhook secrets | ✅ | ✅ | Recommended |
| Signature validation | ✅ | ✅ | Recommended |
| Webhook secrets | ✅ | ✅ | ✅ Implemented |
| Signature validation | ✅ | ✅ | ✅ Implemented |
| Delivery logs | ✅ | ✅ | Recommended |
| Public API | ✅ | ✅ | **Required** |
| Token auth | ✅ | ✅ | **Required** |
Expand Down
35 changes: 9 additions & 26 deletions docs/GITHUB_SETUP.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,9 @@ cargo run --release -- --enable-unsafe-all-submit
- Replace `your-server` with your actual server address
- Use port `8080` (or your configured server port)
- **Content type:** `application/json`
- **Secret:** Leave empty (signature validation not yet implemented)
- **Secret:** Enter a strong random token (generate with `openssl rand -hex 32`).
Add the same value as `webhook_secret` in Sashiko's `Settings.toml`.
See the [Webhook Security Guide](WEBHOOK_SECURITY.md#github-hmac-setup) for details.
- **Which events would you like to trigger this webhook?**
- Select: **Let me select individual events**
- Check: ✓ **Pull requests**
Expand Down Expand Up @@ -241,32 +243,13 @@ Check the web UI at `http://localhost:8080/` to see the review progress.

## Security Considerations

**⚠️ IMPORTANT:** GitHub webhook signature validation is not yet implemented.
Sashiko verifies GitHub webhook signatures using HMAC-SHA256 when
`webhook_secret` is configured. This mitigates forged webhook requests that
could trigger unauthorized reviews.

For production deployments:

1. **Use HTTPS:** Set up a reverse proxy with TLS
```nginx
# Example nginx config
server {
listen 443 ssl;
server_name sashiko.example.com;

ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;

location /api/webhook/ {
proxy_pass http://localhost:8080;
proxy_set_header X-Real-IP $remote_addr;
}
}
```

2. **Implement webhook secrets:** Future enhancement - see GitHub's [webhook security guide](https://docs.github.com/en/webhooks/using-webhooks/validating-webhook-deliveries)

3. **Network isolation:** Run Sashiko on private network and use SSH tunneling or VPN

4. **Rate limiting:** Configure reverse proxy or firewall to prevent abuse
For production deployment instructions, reverse proxy examples, and a
complete security checklist, see the
[Webhook Security Guide](WEBHOOK_SECURITY.md).

## Advanced Configuration

Expand Down
Loading
Loading