PLATO Security Audit Room — automated security auditing as an engine block
Project description
PLATO Security Audit Room
Automated security auditing as a PLATO engine block — the second room in the SuperInstance ecosystem.
What is this?
A PLATO Room that runs security audits on code changes. It follows the PLATO architecture:
| Concept | In this room | |
|---|---|---|
| Sensors | Diff vulnerability patterns, secret detection, dependency scan, file risk scoring | Pull data from GitHub on each tick |
| Actuators | Post security review, block merge, apply security labels | Push decisions back to GitHub |
| Tick loop | Configurable rate (default 0.1 Hz = every 10s) | Polls for PR state changes |
| Alarms | sql_injection, xss_detected, path_traversal, hardcoded_secrets, command_injection, critical_eval |
Fire on policy conditions |
| History | Ring buffer of last 1000 ticks | Full audit trail via history N command |
| Policies | FLUX bytecode rules in policies/ |
Declarative, versioned, reviewable |
The room exposes the standard PLATO wire protocol — any PLATO client can connect, read sensors, check alarms, and trigger actuators.
Quick Start
Standalone (single audit)
export GITHUB_TOKEN=ghp_your_token_here
python -m plato_room_security_audit.room --repo owner/repo --pr 42 --once
Prints a formatted security audit and exits.
Long-lived server
export GITHUB_TOKEN=ghp_your_token_here
python -m plato_room_security_audit.room --repo owner/repo --watch --port 1235
Then connect with any PLATO client:
from plato_core.protocol import PlatoClient
with PlatoClient.connect("localhost", 1235) as client:
welcome = client.recv_response()
print(f"Connected to {welcome.room_id}")
client.send("tick")
tick = client.recv_response()
print(f"Security state: {tick.data}")
client.send("alarm list")
alarms = client.recv_response()
for a in alarms.alarms:
print(f" {a.id}: {a.state}")
GitHub Action
# .github/workflows/security-audit.yml
name: PLATO Security Audit
on:
pull_request:
types: [opened, synchronize]
jobs:
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- run: pip install plato-core plato-room-security-audit
- run: |
python -m plato_room_security_audit.room \
--repo ${{ github.repository }} \
--pr ${{ github.event.pull_request.number }} \
--once
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Security Checks
All checks are heuristic (no LLM, no AST parsing) — fast, deterministic, and auditable.
| Check | What it catches | Severity |
|---|---|---|
sql_injection |
String concatenation in SQL execute() calls, f-string queries | critical |
xss |
Unescaped output in templates, innerHTML with variables |
critical |
path_traversal |
../ patterns in file paths, unsanitized open() calls |
critical |
hardcoded_secrets |
AWS keys, API keys, private keys, JWTs, Slack tokens, DB URLs | critical |
command_injection |
os.system(), subprocess with shell=True, eval() with user input |
critical |
insecure_random |
random module for security-sensitive contexts |
error |
insecure_crypto |
MD5, SHA1, DES, ECB mode for crypto operations | error |
debug_enabled |
DEBUG = True in settings files |
warning |
http_not_https |
HTTP (not HTTPS) URLs in source code | warning |
disabled_security |
Comments disabling security checks, # noqa, @SuppressWarnings |
warning |
sensitive_file_exposure |
Changes to .env, secrets files, key files |
error |
weak_password_hash |
hashlib.md5/sha1 for password hashing |
error |
Adding a check
# auditor.py
@check
def check_insecure_deserialization(diff: str) -> CheckResult:
"""Detect unsafe deserialization patterns."""
for line in diff.splitlines():
if line.startswith("+") and "pickle.loads" in line:
return CheckResult(
check_id="insecure_deserialization",
name="Insecure Deserialization",
passed=False,
message="pickle.loads() can execute arbitrary code",
severity="critical",
)
return CheckResult(
check_id="insecure_deserialization",
name="Insecure Deserialization",
passed=True,
message="No unsafe deserialization detected.",
)
The @check decorator registers it automatically.
Audit Reports
The report.py module generates structured markdown reports with severity ratings:
from plato_room_security_audit.auditor import run_all_checks
from plato_room_security_audit.report import generate_report, generate_short_summary
results = run_all_checks(diff)
# Full markdown report with CVSS-style severity ratings
report = generate_report(results, repo="owner/repo", pr_number=42, author="alice")
# One-line summary for status checks
summary = generate_short_summary(results)
# → "PLATO Security: 10/12 checks passed — BLOCK_MERGE"
Configuration
Configure checks via plato-security.yml in your repo root:
checks:
sql_injection: enabled
xss: enabled
path_traversal: enabled
hardcoded_secrets: enabled
command_injection: enabled
insecure_random: enabled
insecure_crypto: enabled
debug_enabled: warn
http_not_https: warn
disabled_security: warn
sensitive_file_exposure: enabled
weak_password_hash: enabled
Options: enabled, disabled, warn (report but don't block).
FLUX Policies
Policies live in policies/*.flx:
POLICY no_sql_injection {
SENSE sql_injection_count
GUARD sql_injection_count > 0
ALARM severity=critical
ACTUATE block_merge
EMIT "SQL injection vulnerability detected"
}
Policy files
| File | Triggers when |
|---|---|
no_sql_injection.flx |
SQL injection patterns detected in diff |
no_command_injection.flx |
os.system(), eval(), shell=True with interpolation |
no_hardcoded_secrets.flx |
Secret-like patterns in added lines |
no_path_traversal.flx |
Path traversal patterns in file operations |
Architecture
PLATO Wire Protocol
(TCP, JSON lines)
│
┌────────────────┼────────────────┐
│ │ │
tick command alarm list actuator cmd
│ │ │
▼ ▼ ▼
┌─────────────────────────────────────────────┐
│ Security Audit Room │
│ │
│ Sensors Actuators │
│ ├─ vuln_patterns ├─ post_audit │
│ ├─ secret_scan ├─ block_merge │
│ ├─ risk_score └─ apply_label │
│ └─ dependency_check │
│ │
│ Alarms │
│ ├─ sql_injection (sql_injection_count > 0)│
│ ├─ xss_detected (xss_count > 0) │
│ ├─ path_traversal (traversal_count > 0) │
│ ├─ hardcoded_secrets (secret_count > 0) │
│ ├─ command_injection (cmd_injection > 0) │
│ └─ critical_eval (eval_count > 0) │
│ │
│ History (1000-tick ring buffer) │
└─────────────────────────────────────────────┘
│
GitHub API
│
┌────────────────┼────────────────┐
│ │ │
Fetch diff Fetch metadata Post audit
Scan patterns Fetch file list Block merge
Testing
pip install -e ".[test]"
pytest tests/ -v
All tests use simulated data — no GitHub API calls needed.
License
MIT — see LICENSE.
Part of
SuperInstance — the PLATO ecosystem.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file plato_room_security_audit-0.1.1.tar.gz.
File metadata
- Download URL: plato_room_security_audit-0.1.1.tar.gz
- Upload date:
- Size: 30.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.15
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
50fd47c1a246e4451636546b9bc6c4914e67a674fbde28605a579bd5f7de0c20
|
|
| MD5 |
7b1f14c6114108d87290bb9e8614b1d9
|
|
| BLAKE2b-256 |
f885aaa8d8e2facef5faac137d674d3b8466a8cf046a8b4f22b563558411613c
|
File details
Details for the file plato_room_security_audit-0.1.1-py3-none-any.whl.
File metadata
- Download URL: plato_room_security_audit-0.1.1-py3-none-any.whl
- Upload date:
- Size: 23.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.15
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8bbbb9a4c8464cd0c6b0b6afb4b6b0d92cadcfecab9ce16c6fc5f9cd5d512de2
|
|
| MD5 |
58932dc1ab7eff8994ac42b53d06a43c
|
|
| BLAKE2b-256 |
c198e36d1e65b33924454ab9940fba2432d87a3ce3906f795debccbacc9e4bd6
|