Skip to main content

Scans a branch before merge and tells you exactly how badly it's going to hurt.

Project description

PayloadGuard

Scans a branch before merge and tells you exactly how badly it's going to hurt.

dev: Dark^Vader


Contents


Install

pip install payloadguard-plg

Or from source:

pip install -r requirements.txt

Python 3.8+. Core deps: GitPython, PyYAML, PyJWT, requests. Layer 4 multi-language analysis requires tree-sitter grammar packages (included in requirements.txt โ€” omit any you don't need, unsupported file types are skipped silently).


Run

python analyze.py <repo_path> <branch> [target]
# Basic scan
python analyze.py . feature-branch main

# Feed it the PR description โ€” catches deceptive payloads
python analyze.py . feature-branch main --pr-description "minor syntax fix"

# Save a JSON report
python analyze.py . feature-branch main --save-json

# Save a markdown report
python analyze.py . feature-branch main --save-markdown reports/scan.md

python analyze.py --help if you need it.


The forensic report

Every scan produces the same structured report. Here's what each section tells you and what to look for.


๐Ÿ“… Temporal

How old the branch is relative to the target, and which commits are being compared. A branch that's been sitting open for months while the target keeps moving is already a problem before you look at a single line.

๐Ÿ“… TEMPORAL
   Branch age: 14 days
   Branch: a1b2c3d (2026-04-08)
   Target:  e4f5g6h (2026-04-22)

๐Ÿ“ File changes

Raw scope of the changeset โ€” files added, deleted, modified. Deletions are the number to watch. A PR that adds 2 files and deletes 40 is not a normal PR.

๐Ÿ“ FILE CHANGES
   Added:      3
   Deleted:    1
   Modified:   5
   Total:      9

๐Ÿ“ Line changes

Volume and direction of change. Deletion ratio is the derived signal โ€” what fraction of total churn is removal. Above 50% starts raising flags; above 90% means almost everything this PR touches is being taken away.

๐Ÿ“ LINE CHANGES
   Added:        420 lines
   Deleted:       18 lines
   Net:          402 lines
   Deletion ratio: 4.1%

๐Ÿงฌ Structural drift โ€” Layer 4

Parses every modified source file and computes exactly which named classes and functions disappeared. This is the layer that catches a file being "modified" when it's actually been gutted โ€” line diffs alone won't tell you that AuthManager and SessionStore no longer exist.

Flags CRITICAL only when both conditions are met: deletion ratio exceeds the threshold and enough nodes were deleted. The dual gate prevents noise from small utility files.

Supported languages: Python ยท JavaScript ยท TypeScript ยท Go ยท Rust ยท Java (.py .js .jsx .ts .tsx .go .rs .java). Python uses stdlib AST. All others use tree-sitter โ€” files for grammars not installed are skipped silently.

๐Ÿงฌ STRUCTURAL DRIFT (Layer 4)
   Overall severity: LOW
   Max deletion ratio: 0.0%

If something is actually being removed at scale:

๐Ÿงฌ STRUCTURAL DRIFT (Layer 4)
   Overall severity: CRITICAL
   src/core/auth.py: 8 nodes deleted (80.0%) [CRITICAL]
      - AuthManager
      - SessionStore
      - TokenValidator

โฑ Temporal drift โ€” Layer 5a

Compound score: branch_age_days ร— target_commits_per_day. Raw age alone is a weak signal โ€” a 90-day branch on a slow repo is nothing; on a fast repo it's a serious semantic gap. The drift score accounts for both.

Status Drift Score Meaning
CURRENT < 250 Branch context is valid
STALE 250 โ€“ 999 Moderate drift โ€” manual diff review
DANGEROUS โ‰ฅ 1000 Rebase required before this goes anywhere near main
โฑ  TEMPORAL DRIFT (Layer 5a)
   Status: CURRENT [LOW]
   Drift Score: 14.0
   Target velocity: 1.0 commits/day
   โœ“ SAFE. Branch context is synchronized with target.

๐Ÿ”Ž Semantic transparency โ€” Layer 5b

Compares the PR description against the verified severity. If the description uses low-impact language ("minor fix", "typo", "cleanup") but the structural verdict is CRITICAL, that's a DECEPTIVE_PAYLOAD. Advisory signal โ€” doesn't override the main verdict, but it shows up clearly.

Status Meaning
TRANSPARENT Description matches what the diff actually does
UNVERIFIED No description provided
DECEPTIVE_PAYLOAD Description claims low impact, diff says otherwise
๐Ÿ”Ž SEMANTIC TRANSPARENCY (Layer 5b)
   Status: TRANSPARENT
   โœ“ SAFE. PR description aligns with verified structural impact.

๐Ÿ” Verdict

The final call. Produced by the consequence model (Layer 3) which accumulates a weighted score across all signals. No single threshold triggers it โ€” it's the combination that matters.

Verdict Severity Score Meaning
SAFE LOW 0 Nothing notable. Proceed.
REVIEW MEDIUM 1โ€“2 Minor flags. Worth a look but not alarming.
CAUTION HIGH 3โ€“4 Real signals. Needs proper review before merge.
DESTRUCTIVE CRITICAL โ‰ฅ 5 Stop. Do not merge.
๐Ÿ” VERDICT: SAFE [LOW]
   โš ๏ธ  No major red flags detected

โœ‰๏ธ  RECOMMENDATION:
   โœ“ Proceed with normal review process

Exit codes

Code Meaning
0 Fine
1 Analysis broke
2 Do not merge โ€” wire this to block CI

CI

GitHub Action (recommended)

Add to .github/workflows/payloadguard.yml:

name: PayloadGuard

on:
  pull_request:
    types: [opened, synchronize, reopened]

jobs:
  scan:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      pull-requests: write

    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: PayloadGuard
        id: payloadguard
        uses: DarkVader-PLG/payload-consequence-analyser@v1
        with:
          repo-token: ${{ secrets.GITHUB_TOKEN }}
          pr-description: ${{ github.event.pull_request.body }}

      - name: Enforce verdict
        if: always()
        env:
          EXIT_CODE: ${{ steps.payloadguard.outputs.exit-code }}
        run: |
          if [ "$EXIT_CODE" = "1" ]; then exit 1; fi
          if [ "$EXIT_CODE" = "2" ]; then exit 2; fi

With GitHub App secrets wired up, pass them too:

      - name: PayloadGuard
        uses: DarkVader-PLG/payload-consequence-analyser@v1
        with:
          repo-token: ${{ secrets.GITHUB_TOKEN }}
          pr-description: ${{ github.event.pull_request.body }}
          app-id: ${{ secrets.PAYLOADGUARD_APP_ID }}
          private-key: ${{ secrets.PAYLOADGUARD_PRIVATE_KEY }}
          installation-id: ${{ secrets.PAYLOADGUARD_INSTALLATION_ID }}

Wire a branch protection rule to require the scan check and the merge button is blocked on DESTRUCTIVE verdicts.


GitHub App

For a named PayloadGuard check badge in the PR checks tab (beyond the sticky comment), register a GitHub App and wire it up with three repo secrets:

Secret Value
PAYLOADGUARD_APP_ID Your App ID
PAYLOADGUARD_PRIVATE_KEY Contents of the generated .pem private key
PAYLOADGUARD_INSTALLATION_ID Installation ID from github.com/settings/installations

With those set, the workflow calls post_check_run.py to post a Check Run after each scan โ€” green for SAFE, red for DESTRUCTIVE, with the full report as the body.

Without the secrets the step is a no-op; the sticky comment and merge blocking still work.


Configuration

Drop a payloadguard.yml in your repo root. Everything is optional โ€” omit what you don't care about and the defaults hold.

# payloadguard.yml
thresholds:
  branch_age_days: [90, 180, 365]      # score goes up at each
  files_deleted:   [10, 20, 50]
  lines_deleted:   [5000, 10000, 50000]
  temporal:
    stale:     250                      # drift score = age ร— commits/day
    dangerous: 1000
  structural:
    deletion_ratio:    0.20             # fraction of AST nodes deleted
    min_deleted_nodes: 3               # both must be hit to flag CRITICAL

semantic:
  benign_keywords:
    - minor fix
    - minor syntax fix
    - typo
    - formatting
    - cleanup
    - small tweak

Tighten it for anything that matters:

thresholds:
  structural:
    deletion_ratio: 0.10
    min_deleted_nodes: 2
semantic:
  benign_keywords:
    - minor fix
    - typo
    - trivial
    - nit

How it works

Five layers. Every scan, every time.

Layer What it checks
1 โ€” Surface Scan Files and lines changed
2 โ€” Forensic Analysis Deletion ratio, critical path detection
3 โ€” Consequence Model Weighted score โ†’ final verdict
4 โ€” Structural Drift AST/tree-sitter diff โ€” which classes and functions actually disappeared (Python, JS, TS, Go, Rust, Java)
5a โ€” Temporal Drift Branch age ร— repo velocity
5b โ€” Semantic Transparency Does the PR description match what the diff actually does

Scoring (Layer 3)

Points accumulate across signals. No single threshold kills you โ€” it's the pile-up that matters.

Signal Thresholds Points
Branch age > 90 / 180 / 365 days 1 / 2 / 3
Files deleted > 10 / 20 / 50 1 / 2 / 3
Deletion ratio > 50% / 70% / 90% 1 / 2 / 3
Structural severity CRITICAL 3
Lines deleted > 5k / 10k / 50k 1 / 2 / 3

โ‰ฅ 5 โ†’ DESTRUCTIVE. 3โ€“4 โ†’ CAUTION. 1โ€“2 โ†’ REVIEW. 0 โ†’ SAFE.


The incident

In April 2026, a developer received a Codex suggestion described as a "minor syntax fix". The branch had been open for 10 months. Nobody looked closely enough. It would have deleted 60 files, 11,967 lines, 217 tests, and the entire application architecture in a single merge. That's what this tool was built to stop.

Below is the forensic report PayloadGuard would have produced on that branch.

======================================================================
PAYLOADGUARD ANALYSIS: codex-suggestion โ†’ main
======================================================================

๐Ÿ“… TEMPORAL
   Branch age: 312 days
   Branch: fa3c21d (2025-06-04)
   Target:  b87e90a (2026-04-22)

๐Ÿ“ FILE CHANGES
   Added:      2
   Deleted:   61
   Modified:   4
   Total:     67

๐Ÿ“ LINE CHANGES
   Added:        214 lines
   Deleted:   11,967 lines
   Net:       -11,753 lines
   Deletion ratio: 98.2%

๐Ÿงฌ STRUCTURAL DRIFT (Layer 4)
   Overall severity: CRITICAL
   Max deletion ratio: 94.0%
   src/core/auth.py: 12 nodes deleted (94.0%) [CRITICAL]
      - AuthManager
      - SessionStore
      - TokenValidator
      - PermissionGate
      - RoleRegistry

โฑ  TEMPORAL DRIFT (Layer 5a)
   Status: DANGEROUS [CRITICAL]
   Drift Score: 3120.0
   Target velocity: 10.0 commits/day
   โŒ DO NOT MERGE. Extreme semantic drift detected. Mandatory rebase
      and manual architectural review required.

๐Ÿ”Ž SEMANTIC TRANSPARENCY (Layer 5b)
   Status: DECEPTIVE_PAYLOAD
   Matched keyword: "minor syntax fix"
   โŒ DO NOT MERGE. PR description deliberately contradicts catastrophic
      architectural changes.

๐Ÿ” VERDICT: DESTRUCTIVE [CRITICAL]
   โš ๏ธ  Branch is 312 days old (6+ months)
   โš ๏ธ  61 files would be deleted (massive scope)
   โš ๏ธ  Deletion ratio: 98.2% (almost entire changeset is deletions)
   โš ๏ธ  Structural drift CRITICAL โ€” significant class/function deletions detected
   โš ๏ธ  11,967 lines would be deleted (large codebase change)

โœ‰๏ธ  RECOMMENDATION:
   โŒ DO NOT MERGE โ€” This would catastrophically alter the codebase

๐Ÿ—‘๏ธ  DELETED FILES (61 total)

   CRITICAL DELETIONS:
      - tests/test_auth.py
      - tests/test_core.py
      - tests/test_integration.py
      - .github/workflows/ci.yml
      - src/core/auth.py
      - src/core/engine.py
      - requirements.txt

   OTHER DELETIONS:
      - src/modules/session.py
      - src/modules/permissions.py
      - src/modules/roles.py
      ... and 51 more files

======================================================================

Every signal was there. The age. The deletion ratio. The structural wipeout. The gap between what the description said and what the diff actually did. Nobody saw it. Now you will.


PayloadGuard โ€” because AI doesn't feel bad about what it breaks.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

payloadguard_plg-1.0.2.tar.gz (23.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

payloadguard_plg-1.0.2-py3-none-any.whl (20.3 kB view details)

Uploaded Python 3

File details

Details for the file payloadguard_plg-1.0.2.tar.gz.

File metadata

  • Download URL: payloadguard_plg-1.0.2.tar.gz
  • Upload date:
  • Size: 23.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.15

File hashes

Hashes for payloadguard_plg-1.0.2.tar.gz
Algorithm Hash digest
SHA256 54a6cbd4d7036be3e78623266ca80458ec5d4f889d7ca328ac6c8b9b784b9e79
MD5 badd32107f91385bad7f114ba635753a
BLAKE2b-256 dd76a40d3f112f39b0ec1b18ee003d8d86ad3d14a5b307c4b040823e208e2828

See more details on using hashes here.

File details

Details for the file payloadguard_plg-1.0.2-py3-none-any.whl.

File metadata

File hashes

Hashes for payloadguard_plg-1.0.2-py3-none-any.whl
Algorithm Hash digest
SHA256 c00d3673b8f3859826aaecb267f95aab8efe1e9a341a63cef6bd79a985412436
MD5 c161cb33a1d79040a8669889bc39ab1f
BLAKE2b-256 150fb7f6c96b9fb6cd8b99176a5ecd33065942a39bc0c54e6648fae50a502569

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page