Skip to content
Merged
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
20 changes: 20 additions & 0 deletions .github/workflows/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,26 @@ Simple workflow for testing the self-hosted runner setup.
2. Echoes test messages
3. Verifies runner connectivity and permissions

## Privacy Scan Workflow

### privacy-scan

Scans incoming changes for secrets and PII before merge/deploy.

**Triggers:**
- Push
- Pull request (non-fork PRs only)
- Manual (`workflow_dispatch`)

**Behavior:**
1. Runs on self-hosted runner
2. Executes gitleaks for secret detection
3. Runs `scripts/pii_guard.py` on added lines in diff range
4. Fails when medium/high-confidence PII is detected (email/phone/cpf/cnpj/credit-card)

**Allowlist:**
- Use `.pii-allowlist` with regex entries for explicit, reviewed exceptions.

## Deployment Workflow

### deploy-to-home-server
Expand Down
59 changes: 59 additions & 0 deletions .github/workflows/privacy-scan.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
name: Privacy Scan

on:
push:
pull_request:
workflow_dispatch:

jobs:
scan:
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false
runs-on: self-hosted
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Cleanup stale gitleaks temp file
run: |
sudo rm -f /tmp/gitleaks.tmp || rm -f /tmp/gitleaks.tmp || true

- name: Run gitleaks
uses: gitleaks/gitleaks-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Determine diff range
id: range
run: |
if [ "${{ github.event_name }}" = "pull_request" ]; then
echo "base=${{ github.event.pull_request.base.sha }}" >> "$GITHUB_OUTPUT"
echo "head=${{ github.event.pull_request.head.sha }}" >> "$GITHUB_OUTPUT"
echo "three_dot=true" >> "$GITHUB_OUTPUT"
exit 0
fi

BEFORE="${{ github.event.before }}"
if [ -z "$BEFORE" ] || [ "$BEFORE" = "0000000000000000000000000000000000000000" ]; then
BEFORE=$(git rev-list --max-parents=0 HEAD)
fi

echo "base=$BEFORE" >> "$GITHUB_OUTPUT"
echo "head=${{ github.sha }}" >> "$GITHUB_OUTPUT"
echo "three_dot=false" >> "$GITHUB_OUTPUT"

- name: Run PII guard on added lines
run: |
if [ "${{ steps.range.outputs.three_dot }}" = "true" ]; then
python3 scripts/pii_guard.py \
--base "${{ steps.range.outputs.base }}" \
--head "${{ steps.range.outputs.head }}" \
--three-dot \
--fail-on medium
else
python3 scripts/pii_guard.py \
--base "${{ steps.range.outputs.base }}" \
--head "${{ steps.range.outputs.head }}" \
--fail-on medium
fi
36 changes: 36 additions & 0 deletions .opencode/plugins/pii-commit-guard.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { execFileSync } from "node:child_process"
import { existsSync } from "node:fs"
import path from "node:path"

function isGitCommitCommand(command) {
if (!command || typeof command !== "string") return false
const compact = command.replace(/\s+/g, " ").trim().toLowerCase()
return /(^|[;&|]\s*|&&\s*)git commit(\s|$)/.test(compact)
}

export const PiiCommitGuard = async ({ directory }) => {
const root = directory

return {
"tool.execute.before": async (input, output) => {
if (input.tool !== "bash") return

const command = String(output?.args?.command || "")
if (!isGitCommitCommand(command)) return

const guardScript = path.join(root, "scripts", "pii_guard.py")
if (!existsSync(guardScript)) {
throw new Error("PII guard script not found at scripts/pii_guard.py")
}

try {
execFileSync("python3", [guardScript, "--staged", "--fail-on", "medium"], {
cwd: root,
stdio: "inherit",
})
} catch {
throw new Error("Commit blocked by deterministic PII guard (scripts/pii_guard.py)")
}
},
}
}
30 changes: 30 additions & 0 deletions .opencode/skills/pii-commit-check/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
---
name: pii-commit-check
description: Run staged PII checks before creating commits.
compatibility: opencode
---

# PII Commit Check

Use this skill whenever preparing a commit.

## Workflow

1. Run deterministic scan on staged changes:

```bash
python3 scripts/pii_guard.py --staged --fail-on medium
```

2. Run manual agentic review in the current OpenCode session (uses current configured model/provider, no extra API key):

- Ask OpenCode to inspect staged diff for PII risks before commit.
- Example prompt:
- "Review `git diff --cached` for possible PII leaks (CPF/CNPJ/phone/email/card/personal identifiers). Flag high-confidence risks and suggest redactions."

3. If any check fails:
- Block commit.
- Show offending lines/findings.
- Ask for redaction or explicit allowlist update in `.pii-allowlist`.

4. Only proceed with `git commit` after all checks pass.
5 changes: 5 additions & 0 deletions .pii-allowlist
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Regex allowlist for pii_guard.py findings.
# One regex per line. Matches against:
# file_path:line_number:kind:value:line_text
#
# Keep this file minimal and documented when adding exceptions.
8 changes: 8 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,11 @@ repos:
rev: v8.24.2
hooks:
- id: gitleaks

- repo: local
hooks:
- id: pii-guard
name: pii-guard
entry: python3 scripts/pii_guard.py --staged --fail-on medium
language: system
pass_filenames: false
14 changes: 10 additions & 4 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -405,12 +405,15 @@ On container start, the workspace sync script (`scripts/workspace-sync.sh`) clon

## Security: Secret Scanning

This repository uses **gitleaks** to prevent accidental commits of secrets (API keys, tokens, passwords, etc.).
This repository uses **gitleaks** and a **PII guard** to reduce accidental exposure of secrets and personal data.

### Automated Scanning

- **CI/CD:** Every push to any branch is scanned via GitHub Actions (`.github/workflows/gitleaks.yml`)
- **CI/CD:** Every push to any branch is scanned via GitHub Actions (`.github/workflows/privacy-scan.yml`)
- **Local:** Pre-commit hooks scan staged changes before each commit (optional but recommended)
- **Privacy guard:** Added-line PII checks run in pre-commit and in CI via `.github/workflows/privacy-scan.yml`
- **OpenCode runtime guard:** `opencode.json` loads `.opencode/plugins/pii-commit-guard.mjs`, which intercepts `git commit` bash calls and runs staged PII checks before allowing commit execution
- **OpenCode commit behavior (mandatory):** whenever a commit is requested in this repo, OpenCode must run the `pii-commit-check` skill before creating the commit

### Setup Pre-commit Hooks (One-Time)

Expand All @@ -426,9 +429,12 @@ This script creates `venv/` if needed, installs pre-commit, and installs git hoo
# Normal commit - gitleaks runs automatically
git commit -m "your message"

# Skip gitleaks (emergency only)
SKIP=gitleaks git commit -m "your message"
# Skip checks (emergency only)
SKIP=gitleaks,pii-guard git commit -m "your message"

# Manual full scan
source venv/bin/activate && pre-commit run gitleaks --all-files

# Manual staged PII scan
python3 scripts/pii_guard.py --staged --fail-on medium
```
6 changes: 6 additions & 0 deletions opencode.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"$schema": "https://opencode.ai/config.json",
"plugin": [
"./.opencode/plugins/pii-commit-guard.mjs"
]
}
Loading
Loading