v2.0.0 · 1,000 rules · 25 languages · SARIF 2.1.0

machin-secure

A security auditor where the tool owns no model — the calling agent is the LLM.
Single ~88 KB binary. Deterministic regex rules. Agent-managed verdicts. CI-native.

The KISS lesson, applied to SAST. A deterministic regex engine over the filesystem finds most of what a heavyweight agent finds — no Docker, no browser, no LLM API client inside the tool. The agent driving secure already has a model; it reads the findings, reasons, and writes back a verdict that persists across scans. Read the vision →

The split — tool owns the scan, agent owns the judgment

Every finding carries a stable id (sha256(rule|file|line)). The tool never calls an LLM to triage its own false positives — the calling agent does it with the model it already has, and persists the call.

#!/ the whole loop, no API key, no network call to a model provider
./secure --target .                          # JSONL findings on stdout
./secure --target . | jq 'select(.severity=="critical")'
# agent reasons: this one is a Vue prop binding, not a secret
./secure verdict --target . <id> drop --reason "Vue prop binding, not a secret"
# future scans suppress it automatically; --show-all brings it back

Same BYOK split as grepapi's /v1/brief: the tool returns structured data, the operator's own LLM completes the reasoning.

Measured, not asserted

Two independent levers, aimed at two different situations — scope for CI, parallelism for full audits.

scan mode10.4k-file repofindings
--diff (8 changed files)0.23ssame 1950
full scan, 1 worker~4m37s1950
full scan, 8 workers1m28s1950

Byte-identical findings every way. --diff is the actual fix for CI latency; the engine didn't need to get faster for this case to be fast.

Usage

./build.sh                                              # machin encode + build -> ./secure

./secure --target ./some/repo                           # JSONL findings on stdout
./secure --target ./some/repo --summary                 # one JSON summary object
./secure --target ./some/repo --sarif                   # SARIF 2.1.0 (GitHub Code Scanning)
./secure --target ./some/repo --diff                    # only working-tree-changed files
./secure --target ./some/repo --diff-base origin/main   # only files changed vs a base ref (PR CI)
./secure --target ./some/repo --workers 16              # parallel full-audit (default: 8)
./secure --target ./some/repo --hart                    # guidance for the agent's own hart report

./secure verdict --target . <id> drop --reason "..."   # persist the agent's judgment
./secure verdict --target . --stdin                     # batch verdicts via JSON Lines

Exit codes: 0 clean · 1 error · 2 high/critical findings present.

1,000 CWE-tagged detections across 25 languages

rules.json is plain data, read fresh from disk every run — extend it without recompiling. The rule pack is the actual product; the engine is interchangeable.

Secrets

CWE-798 AWS, Google, Slack, GitHub PAT, Stripe, JWT, private keys, generic high-entropy, connection strings, config files (.properties, TOML, INI), hardcoded JWT secrets.

Injection

CWE-78/89/95 command injection (shell=True, eval, exec, os.system, Runtime.exec, Command::new), SQL injection (string concat, f-strings, template interp).

Deserialization

CWE-502 pickle, yaml.load, ObjectInputStream, PHP unserialize, Ruby YAML.load.

XSS / SSRF / XXE

CWE-79/918/611 dangerouslySetInnerHTML, innerHTML, document.write, template.HTML, HTTP concat URLs, XML external entities.

Weak crypto / TLS

CWE-327/295 MD5, SHA-1, DES, InsecureSkipVerify, rejectUnauthorized:false, verify=False, trust-all certs, weak TLS versions, JWT alg:none (critical).

Memory safety

CWE-120/242 strcpy, strcat, sprintf, gets, Rust unsafe blocks.

IaC / cloud

CWE-732/668/311 Terraform (public S3, 0.0.0.0/0 SGs, unencrypted RDS), Kubernetes (privileged, runAsUser:0, hostPath, hostNetwork, :latest), Docker (ADD URL, :latest, apt-get upgrade, COPY . .).

Frameworks

CWE-352/489 Django (ALLOWED_HOSTS=*, @csrf_exempt, SECURE_SSL_REDIRECT=False), Flask (debug=True RCE, hardcoded secret_key), FastAPI (CORS *), Spring (actuator exposed), Express (no body limit).

CI/CD pipeline

CWE-285/532/94 pull_request_target with secrets, write-all permissions, secrets in run steps, PR event data in scripts (script injection), continue-on-error masking scans.

Dep / config hardening

CWE-1104/798 unpinned deps (requirements.txt, package.json ^/~, Cargo.toml *, Gemfile), config file secrets (.properties, TOML, INI), npm --no-audit.

Auth / session

CWE-327/916/384 JWT alg:none (4 languages), unsalted password hashes, bcrypt low rounds, session fixation, plaintext password scheme, weak password length, non-crypto random for secrets.

Web / API

CWE-942/601/352 CORS wildcard, open redirect, CSRF, SameSite=None, mass assignment, log injection, ReDoS, info exposure via tracebacks.

API injection variants

CWE-1336/1321/943/90 GraphQL (introspection, no depth/complexity limits), WebSocket origin, prototype pollution, NoSQL injection ($where, $gt), LDAP injection, SSTI (Jinja/EJS), SSRF (urlopen, axios, fetch, file_get_contents).

Crypto misuse

CWE-327/329/780 ECB mode, hardcoded IV/nonce/salt, AES-128/192, RSA <3072, PKCS1v1.5 padding (oracle attacks), deprecated crypto.random, MD5/SHA1 for passwords (5 languages).

PHP injection

CWE-89/95/98/502 SQL with $_GET/$_POST, eval/include/unserialize with user input, file_get_contents SSRF, md5/sha1/crypt for passwords.

Info disclosure

CWE-209/489/532/547 debug console.log, print with secrets, hardcoded IPs, HTTP (not HTTPS) URLs, verbose errors in prod, assert for sensitive checks, file upload without validation.

C/C++ deep dive

CWE-134/120/770 format string attacks (printf/fprintf with variable), scanf %s without bounds, alloca (stack overflow), strtok (not thread-safe), atoi (no error handling), getenv without validation.

Rust / Go / Java

CWE-704/761/74/94 Rust transmute/from_raw/raw pointers/ptr::read/write, Go unsafe.Pointer/cgo/Sprintf-SQL/no-TLS, Java JNDI injection (Log4Shell), SpEL injection, XPath injection, SSRF, XML TransformerFactory (XXE).

Mobile (Android/iOS)

CWE-489/319/749 Android: debuggable=true, cleartext traffic, allowBackup, exported components, WebView JS + addJavascriptInterface (RCE). iOS: ATS disabled, plist secrets.

Rails / Laravel

CWE-352/1336/89/79 Rails: CSRF skip, render with params (SSTI), attr_accessible. Laravel: DB::raw SQL injection, Blade {!! !!} (XSS), env() in code.

Cloud + DB

CWE-732/89/94/943 AWS S3 public-read, IAM wildcard Action/Resource, hardcoded AWS creds. Redis EVAL injection, Elasticsearch query_string injection, ORDER BY injection.

Network + supply chain

CWE-319/1104 FTP/Telnet (cleartext), SMTP without TLS, GitHub Actions @main/@master (not pinned to SHA), npm postinstall scripts, pip install from git URLs.

PythonJS/TSVueGoRustC/C++C#Java/KotlinRubyShellSwiftPHPYAMLTerraformDockerfileXMLTOML.propertiesplistINIrequirements.txtGemfileJSON

CI / GitHub Code Scanning

--sarif emits a SARIF 2.1.0 report (validated against the official schema) that GitHub's Security tab, VS Code, and any SARIF consumer read directly. There's a reusable GitHub Action — drop this into any repo:

# .github/workflows/machin-secure.yml
name: machin-secure
on: [push, pull_request]
permissions:
  contents: read
  security-events: write   # required to upload SARIF
jobs:
  scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: javimosch/machin-secure@v2
        with:
          target: '.'
      - uses: github/codeql-action/upload-sarif@v3
        with:
          sarif_file: machin-secure.sarif
          category: machin-secure

The action exits 0 whether or not findings exist (so the SARIF upload always runs); severity gating is left to GitHub code-scanning settings.

What it is NOT (on purpose)

No LLM client

No OpenAI/OpenRouter key, no budget, no network call to a model provider. The agent driving secure already is an LLM.

No Docker / browser

Read-only static analysis. Never executes target code, never mutates the target repo.

No index / database

The filesystem is scanned fresh every time — freshness over sophistication. The one persistent state is the agent's verdict memory.

No report generation

--hart prints guidance for the agent to author and publish its own HTML report with its own model. The tool doesn't bake prose judgment into a binary that owns none.

Built with

machin / MFL — a language optimized for the AI agent writing it. The scanner is ~500 lines of MFL, compiled to a single ~88 KB static binary. Read the vision & north star →