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
85 changes: 70 additions & 15 deletions .github/workflows/package-test.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
name: CI Test
name: CI

on:
push:
branches:
Expand All @@ -7,28 +8,82 @@ on:
branches:
- main
- nightly
- 'releases/*'
- "releases/*"

permissions:
contents: read

jobs:
Unit-Tests:
timeout-minutes: 10
runs-on: ${{ matrix.os }}
lint:
name: Lint & format
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"

- name: Install Poetry
run: curl -sSL https://install.python-poetry.org | python - -y

- name: Update PATH
run: echo "$HOME/.local/bin" >> $GITHUB_PATH

- name: Install dependencies
run: poetry install --no-interaction

- name: Ruff (lint)
run: poetry run ruff check python_obfuscator tests

- name: Black (format check)
run: poetry run black --check python_obfuscator tests

- name: isort (import order check)
run: poetry run isort --check-only python_obfuscator tests

- name: Pyright (type check)
run: poetry run pyright python_obfuscator

test:
name: Test (Python ${{ matrix.python-version }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
os: [macos-latest, windows-latest, ubuntu-latest]
python-version: [3.6, 3.7, 3.8, 3.9]
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4

- name: Install Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Setup dependencies
allow-prereleases: true

- name: Install Poetry
run: curl -sSL https://install.python-poetry.org | python - -y

- name: Update PATH
run: echo "$HOME/.local/bin" >> $GITHUB_PATH

- name: Install dependencies
run: poetry install --no-interaction

- name: Type check (mypy)
run: poetry run mypy python_obfuscator

- name: Run tests with coverage
run: |
python -m pip install --upgrade pip
pip install pytest
python setup.py install
poetry run coverage run -m pytest
poetry run coverage report
poetry run coverage xml

- name: Run Tests
run: pytest tests
- name: Upload coverage to Codecov
if: matrix.python-version == '3.12'
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
slug: davidteather/python-obfuscator
files: ./coverage.xml
44 changes: 44 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: Publish Package

on:
release:
types: [created]
workflow_dispatch:

permissions:
contents: read
id-token: write

jobs:
pypi-publish:
name: Upload release to PyPI
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}

- name: Set up Python 3.x
uses: actions/setup-python@v5
with:
python-version: "3.x"

- name: Install Poetry
run: |
curl -sSL https://install.python-poetry.org | python - -y

- name: Update PATH
run: echo "$HOME/.local/bin" >> $GITHUB_PATH

- name: Update Poetry configuration
run: poetry config virtualenvs.create false

- name: Install dependencies
run: poetry install --sync --no-interaction

- name: Package project
run: poetry build

- name: Publish package distributions to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
31 changes: 0 additions & 31 deletions .github/workflows/python-publish.yml

This file was deleted.

8 changes: 7 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,10 @@ __pycache__
dist
build
python_obfuscator.egg-info
.pytest_cache
.pytest_cache
.mypy_cache
.ruff_cache
obfuscated/
.coverage
coverage.xml
htmlcov/
56 changes: 56 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
repos:
- repo: https://github.com/psf/black
rev: 25.1.0
hooks:
- id: black
language_version: python3
args: [--line-length=88]

- repo: https://github.com/pycqa/isort
rev: 5.13.2
hooks:
- id: isort
args: [--profile=black]

- repo: https://github.com/pycqa/flake8
rev: 7.0.0
hooks:
- id: flake8
args:
[
"--max-line-length=88",
"--extend-ignore=E203,W503,E501,F401,F541",
]

- repo: local
hooks:
- id: mypy
name: mypy
entry: poetry run mypy python_obfuscator
language: system
pass_filenames: false
always_run: true
stages: [commit]

- id: pytest-unit
name: Run unit tests
entry: poetry run pytest tests/ -v
language: system
pass_filenames: false
always_run: true
stages: [commit]

- id: pytest-coverage-report
name: pytest coverage report (manual)
entry: poetry run pytest tests --cov=python_obfuscator --cov-report=term-missing --cov-report=html
language: system
pass_filenames: false
always_run: true
stages: [manual]

default_language_version:
python: python3

fail_fast: true

default_stages: [commit]
72 changes: 72 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# Changelog

All notable changes to this project will be documented in this file.

The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
This project uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

---

## [0.1.0] — AST-Based Rewrite

Complete rewrite from a regex-based implementation to a fully AST-based pipeline.

### Added

- **`ObfuscationConfig`** — immutable frozen dataclass for selecting techniques.
Factory methods: `all_enabled()`, `only(*names)`, `.without(*names)`, `.with_added(*names)`.
- **Technique registry** — `@register` class decorator; techniques self-register by name.
`all_technique_names()` and `get_transforms(enabled)` for pipeline construction.
- **`variable_renamer`** — two-pass AST renamer. First pass collects renameable names
(excludes builtins, imports, dunders, and attribute-accessed names); second pass applies
the mapping. Also renames `nonlocal` and `global` statement name lists.
- **`string_hex_encoder`** — converts string literals to `bytes.fromhex(…).decode('utf-8')`
call nodes. Skips f-strings where replacing a `Constant` with a `Call` is invalid.
- **`dead_code_injector`** — recursively injects dead variable assignments at every scope
level: module body, function bodies, class bodies, if/for/while/try/with branches.
Some assignments reference earlier dead variables (intra-scope cross-references) to
simulate computation. Accepts `InjectionParams` and a seeded `random.Random` for
reproducible output.
- **`exec_wrapper`** — wraps the entire module in `exec(ast.unparse(tree))`, reducing the
top-level AST to one statement.
- **`Obfuscator` class** — caches the transform pipeline across multiple `obfuscate()` calls.
- **`obfuscate()` module-level helper** — convenience wrapper for one-shot use.
- **CLI** (`pyobfuscate`) — `--disable/-d` flag accepts technique names; `--stdout`; `--version/-V`.
- **E2E test suite** with correctness and benchmark tests across six complex programs.
- **Per-technique runtime benchmarks** showing individual overhead contribution.
- 100 % branch coverage enforced in CI.
- Python 3.10–3.14 test matrix.

### Changed

- Priority ordering now encoded in `TechniqueMetadata.priority` rather than list position.
- `VariableNameGenerator` and `RandomDataTypeGenerator` accept an optional `rng` argument
for deterministic testing.
- `_NameCollector` exposes `all_bound_names` (assigned + imported + builtins) for use by
the dead-code injector's exclusion set.

### Removed

- All regex-based technique implementations (`techniques.py`).
- `regex` runtime dependency (was used only for the old hex-encoding approach).
- `SourceTransform` base class and `source_transforms` package.

### Fixed

- `VariableRenamer` now updates `nonlocal` and `global` statement name lists, preventing
`SyntaxError: no binding for nonlocal '…' found` after renaming.
- `VariableRenamer` excludes attribute-accessed names from renaming to prevent
`AttributeError` when calling methods by the original name after their definition is renamed.
- `_interleave` preserves relative ordering of injected statements (sorted random slots)
so intra-scope cross-references never appear before their definitions.

---

## [0.0.2] — prior release

Initial public release with regex-based obfuscation techniques.

- `one_liner` — collapsed newlines into semicolons (superceded by `exec_wrapper`).
- `variable_renamer` — regex-based name replacement.
- `add_random_variables` — prepended/appended random assignments at module level.
- `str_to_hex_bytes` — regex-based string-to-hex conversion.
Loading
Loading