# Prompt-Injection Linter — prompt-injection-linter

Paste a system message, prompt, and context; get an OWASP LLM–mapped risk analysis, numeric score, and a safer rewrite. Outcome: faster, explainable prompt reviews for compliance and safety teams.

## Features
- OWASP LLM rule mapping with evidence, severity, and concrete advice
- AI-assisted analysis (OpenAI) with low-temperature, deterministic output
- Optional no-AI fallback using local rules and heuristics
- Inputs: System, Prompt, Context; Strictness (low/med/high); Model (gpt-4o-mini, gpt-4.1-mini)
- Outputs: risk-score (0–100), risks list, safer-rewrite, notes
- Run history persisted; dark split-pane UI with blue accent
- Exports: CSV and JSON for a single run or filtered history
- Built-in rate limit (15/min), CSRF protection, secure headers (CSP), prepared statements, output escaping
- Optional daily rules refresh via cron
- Freemium quotas (free 20/day, pro 200/day)

## Requirements
- PHP 8.2+
- MySQL 8.x
- PHP extensions: pdo_mysql, json, mbstring, curl, openssl
- Web server: Nginx or Apache with PHP-FPM
- Optional: cron for scheduled rules refresh

## Inputs and Outputs
- Inputs
  - System (textarea, max 4000, optional)
  - Prompt (textarea, max 8000, optional)
  - Context (textarea, max 8000, optional)
  - Strictness (select: low | med | high; default: med; required)
  - Model (select: gpt-4o-mini | gpt-4.1-mini; default: gpt-4o-mini; required)
- Outputs
  - score (0–100 risk score)
  - risks[]: rule_id, category, severity (low/med/high/critical), evidence, advice
  - rewrite (safer rewrite)
  - notes[] (informational messages, e.g., heuristics used)

## Quick Start
1) Database
- Create a MySQL database and user.
- Import the provided schema (tables: runs, risks, rules, rate_limits). If you don’t have a schema file, create minimal equivalents for those four tables and add necessary indexes/foreign keys.
- Optionally seed rules with an initial rules JSON or run the refresh step below.

2) Environment
- Configure environment variables (see table below). You can set real environment variables or use a .env file loaded by your runtime.

3) Deploy
- Place the one-page app (index.php) in your web root behind PHP-FPM.
- Ensure the app can read environment variables and reach MySQL.
- Serve over HTTPS. Add recommended security headers (see Security section).

4) Optional: Daily rules refresh
- Expose or run a refresh action (HTTP or CLI) and secure it with a token.
- Example cron (HTTP): 0 3 * * * curl -fsS https://your-app/rules-refresh?token=$APP_CRON_TOKEN >/dev/null
- Example cron (CLI): 0 3 * * * php /var/www/html/bin/refresh_rules.php

5) Use it
- Open the app, paste text, choose strictness/model, submit. View findings, rewrite, and export if needed.

## Configuration (env)
| Name | Required | Default | Example | Purpose/Notes |
|---|---|---:|---|---|
| APP_ENV | no | prod | dev | dev enables verbose errors; prod hides them |
| APP_URL | yes | — | https://lint.example.com | Used for CSRF origin checks, links, and CSP |
| APP_SECRET | yes | — | pNfx...k8 | 32+ chars; HMAC for CSRF/cookies/nonces |
| APP_DB_HOST | yes | — | 127.0.0.1 | MySQL host |
| APP_DB_PORT | no | 3306 | 3306 | MySQL port |
| APP_DB_NAME | yes | — | pilinter | Database name |
| APP_DB_USER | yes | — | pilinter | Database user |
| APP_DB_PASS | yes | — | strongpass | Database password |
| APP_DB_CHARSET | no | utf8mb4 | utf8mb4 | Character set |
| APP_RATE_LIMIT_PER_MIN | no | 15 | 15 | Per-IP cap; stored in rate_limits |
| APP_FREE_QUOTA | no | 20 | 20 | Requests/day for free users |
| APP_PRO_QUOTA | no | 200 | 200 | Requests/day for pro users |
| APP_CSP | no | (see Security) | default-src 'self'; ... | Override CSP header if needed |
| APP_CRON_TOKEN | no | — | longrandom | Token to protect /rules-refresh |
| OPENAI_API_KEY | no | — | sk-... | If empty, app runs in rules-only fallback |
| OPENAI_MODEL | no | gpt-4o-mini | gpt-4.1-mini | Default model selection |
| OPENAI_TEMPERATURE | no | 0.1 | 0.1 | Deterministic output |
| OPENAI_BASE_URL | no | https://api.openai.com/v1 | ... | Override for gateways/self-hosted proxies |
| OPENAI_ORG_ID | no | — | org_... | Optional OpenAI org |
| RULES_REFRESH_URL | no | — | https://rules.example.com/llm.json | Source for daily rules sync |

Notes
- Per-minute rate-limits and daily quotas are tracked against an IP-hash (and session when available). Adjust per your needs.
- Rules table stores OWASP LLM-aligned entries (id, category, severity, description, advice, version, source).

## Security Measures
- Content Security Policy (CSP)
  - Recommended strong policy (adapt to your domain): default-src 'self'; base-uri 'none'; object-src 'none'; frame-ancestors 'none'; form-action 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; script-src 'self' 'nonce-{nonce}'; connect-src 'self' https://api.openai.com
  - The app emits a per-request nonce for inline scripts; set the same in the CSP header.
- CSRF protection
  - Double-submit cookie + hidden input, HMAC-signed with APP_SECRET, SameSite=Strict, HttpOnly, Secure.
  - Origin and referer checks for POSTs from APP_URL.
- Prepared statements and output escaping
  - All DB operations use PDO prepared statements.
  - All dynamic HTML is escaped; JSON responses are safely encoded.
- Rate limiting and quotas
  - 15 requests/min (default) per IP; burst handling returns 429.
  - Optional daily quotas for free/pro plans.
- Input validation
  - Maximum lengths enforced: system (4000), prompt/context (8000).
  - Strict whitelists for strictness/model values.
- Secure headers (examples)
  - X-Content-Type-Options: nosniff
  - Referrer-Policy: no-referrer
  - X-Frame-Options: DENY (or use CSP frame-ancestors)
  - Permissions-Policy: interest-cohort=()
  - Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
- Data handling
  - Avoid logging sensitive prompt contents or API keys.
  - IP addresses are hashed before storage in rate_limits.

## How it works (scoring and rules)
- Each finding is mapped to a rule_id and category aligned with OWASP LLM guidance.
- Severity weights contribute to a 0–100 risk score; stricter modes weigh more heavily and surface more categories.
- The safer rewrite is produced by AI when available; in fallback mode, the app synthesizes a conservative rewrite using local rules.

## Exports
- UI: Export buttons appear on the results and history views.
- HTTP endpoints
  - GET /export?format=csv&run_id=123
  - GET /export?format=json&run_id=123
  - Optional filters for history exports: from=YYYY-MM-DD, to=YYYY-MM-DD, severity=high, model=gpt-4o-mini
- CSV includes run metadata, score, and flattened risks; JSON mirrors the internal structure.

## Optional AI behavior and fallback
- With OPENAI_API_KEY set, the app calls the selected model (gpt-4o-mini or gpt-4.1-mini) at temperature 0.1 to:
  - Identify risks with evidence and map to OWASP-aligned rules
  - Propose a safer rewrite
- If AI is unavailable or disabled:
  - The app performs rules-only analysis using the local rules table and heuristics.
  - score, risks, and notes are still produced; rewrite is generated conservatively.
  - A note is added indicating “AI disabled; using fallback.”

## Data model (tables)
- runs: one row per submission; stores inputs metadata, selected strictness/model, score, rewrite, notes, timestamps.
- risks: one-to-many from runs; each row is a rule finding with severity, evidence, advice.
- rules: canonical OWASP LLM–aligned rule set; updatable via refresh.
- rate_limits: rolling counters keyed by hashed IP and window.

## Acceptance checklist
- [ ] PHP 8.2+ and MySQL 8 available; PDO MySQL extension enabled
- [ ] DB schema created (runs, risks, rules, rate_limits) and app can connect
- [ ] APP_SECRET, APP_URL, and APP_DB_* configured
- [ ] CSP, HSTS, and other security headers applied; inline scripts use a nonce
- [ ] CSRF token validated on POST; invalid token returns 403
- [ ] Rate limit enforced at 15/min with 429 on excess
- [ ] Submissions produce score, risks list, safer rewrite, and notes
- [ ] History view shows prior runs; individual run export works (CSV/JSON)
- [ ] OPENAI_* configured and AI path exercised; fallback path works when key is unset
- [ ] Optional cron refresh populates/updates rules without errors
- [ ] No sensitive secrets or prompt contents are logged

## License
MIT (see LICENSE)