Skip to content
Closed
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
14 changes: 14 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
.git
.github
venv
.venv
build
dist
*.egg-info
__pycache__
*.pyc
.pytest_cache
.ruff_cache
.mypy_cache
tests
*.gif
11 changes: 11 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
version: 2
updates:
- package-ecosystem: pip
directory: /
schedule:
interval: weekly
open-pull-requests-limit: 5
- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
184 changes: 184 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
name: CI

on:
push:
branches: [main]
pull_request:

# Least privilege: nothing here needs to write to the repository.
permissions:
contents: read

concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true

jobs:
test:
name: Tests (Python ${{ matrix.python-version }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: ["3.10", "3.12"]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: pip
- name: Install deps
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt -r requirements-web.txt
pip install pytest==9.1.1 setuptools==80.9.0
- name: Tests
run: python -m pytest tests/ -q
- name: Round-trip matrix (depths 1-4 + corruption)
run: |
python - <<'PY'
import io
from PIL import Image
from core.image.lsb import LSBEncoder
def png():
img=Image.new("RGB",(64,64),(10,20,30))
b=io.BytesIO(); img.save(b,format="PNG"); return b.getvalue()
c=png()
for depth in (1,2,3,4):
for plen in (5,21,22):
data=b'X'*plen
s=LSBEncoder().encode(c,data,depth=depth)
assert LSBEncoder().decode(s,depth=depth)==data, (depth,plen)
print("roundtrip matrix ok")
PY

quality:
name: Lint, types, audit, build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Commits carry a real author (GitHub attributes by e-mail)
if: github.event_name == 'pull_request'
run: |
bad="$(git log --format='%h %ae' "${{ github.event.pull_request.base.sha }}..HEAD" | grep -E '@(localhost|example\.(com|org))$|^[0-9a-f]+ *$' || true)"
if [ -n "$bad" ]; then
echo "Placeholder or missing author e-mail on:"; echo "$bad"
echo "Fix with: git rebase -r <base> --exec 'git commit --amend --no-edit --reset-author'"
exit 1
fi
- uses: actions/setup-python@v5
with:
python-version: "3.10"
cache: pip
- name: Install deps
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt -r requirements-web.txt
pip install ruff==0.17.1 mypy==2.4.0 build twine
- name: Lint
run: ruff check core detect protocol web stegoforge.py stegoforge_ops scripts
- name: Typecheck
run: mypy core detect protocol stegoforge_ops --ignore-missing-imports
- name: SBOM (CycloneDX)
run: |
python scripts/make_sbom.py > sbom.cdx.json
python -c "import json; d=json.load(open('sbom.cdx.json')); assert d['bomFormat']=='CycloneDX'; print(len(d['components']), 'components')"
- name: Upload SBOM
uses: actions/upload-artifact@v7
with:
name: sbom-cdx
path: sbom.cdx.json
- name: Every encoder is importable and the bundled assets exist
run: |
python - <<'PY'
from stegoforge import get_encoder
for m in ["lsb","adaptive-lsb","fingerprint-lsb","dct","alpha","palette",
"audio-lsb","phase","spectrogram","unicode","linguistic","pdf",
"docx","xlsx","office","elf","pe","video-dct","video-motion",
"tcp-covert","timing-covert"]:
get_encoder(m)
from pathlib import Path
assert Path("web/templates/index.html").exists()
assert Path("models/model_quantized.onnx").exists()
assert Path("assets.sha256").exists()
print("assets ok")
PY
- name: Build sdist + wheel
run: |
python -m build
twine check dist/*
- name: Distributions contain the program (package, model, web assets)
run: python scripts/check_dist.py dist/*
- name: The wheel installs and runs in a clean environment
run: |
python -m venv /tmp/clean
/tmp/clean/bin/pip install --quiet dist/*.whl
cd "$(mktemp -d)"
/tmp/clean/bin/stegoforge doctor
/tmp/clean/bin/stegoforge schema --json capacity | head -5

audit:
name: Dependency audit (Python 3.12)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
# Resolved on 3.12 on purpose: Pillow 13 (which fixes 24 advisories in 12.3.0) needs Python >= 3.11,
# so on 3.10 there is nothing patched to resolve to. See "Python 3.10" in the README.
- name: Audit the runtime requirements (blocking)
run: |
python -m pip install --upgrade pip pip-audit==2.10.1
pip-audit --desc -r requirements.txt -r requirements-web.txt

docker:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build Docker image
run: docker build -t stegoforge:ci .
- name: Docker smoke (schema, doctor; offline)
run: |
docker run --rm -e STEGOFORGE_OFFLINE=1 stegoforge:ci stegoforge schema --json capacity | head -5
docker run --rm -e STEGOFORGE_OFFLINE=1 stegoforge:ci stegoforge doctor
- name: Web UI answers on the published port and refuses a foreign Host
run: |
docker run -d --name sf -p 5000:5000 -e STEGOFORGE_WEB_TOKEN=ci-token stegoforge:ci
for i in $(seq 1 30); do curl -fsS http://localhost:5000/ >/dev/null && break || sleep 1; done
curl -fsS http://localhost:5000/ >/dev/null
test "$(curl -s -o /dev/null -w '%{http_code}' -H 'Host: evil.example' http://localhost:5000/)" = "403"
test "$(curl -s -o /dev/null -w '%{http_code}' -X POST http://localhost:5000/capacity)" = "401"
docker rm -f sf

deb:
name: Debian package
runs-on: ubuntu-latest
needs: [test]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.10"
- name: Build the .deb
run: |
python -m pip install --upgrade pip setuptools
python scripts/build_deb.py --out dist-deb
dpkg-deb --info dist-deb/*.deb
dpkg-deb --contents dist-deb/*.deb | awk '{print $1, $2, $6}' | head -60
- name: Install it with apt (resolves Depends on this distribution) and run it
run: |
sudo apt-get update
sudo apt-get install -y ./dist-deb/stegoforge_*.deb
cd "$(mktemp -d)"
stegoforge --help >/dev/null
stegoforge doctor
# /usr/bin/python3: the system interpreter the .deb runs on, not setup-python's toolchain (no Pillow there)
/usr/bin/python3 -c "from PIL import Image; import os; Image.frombytes('RGB',(64,64),os.urandom(64*64*3)).save('c.png')"
echo "hello from the deb" > p.txt
stegoforge encode -c c.png -p p.txt -k 'correct-horse-battery-29!' --json >/dev/null
stegoforge decode -f c_stego.png -k 'correct-horse-battery-29!' -o out.txt --json >/dev/null
test "$(cat out.txt)" = "hello from the deb"
man -w stegoforge
56 changes: 45 additions & 11 deletions .github/workflows/publish-pypi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@ on:
- "v*"
workflow_dispatch:

permissions:
contents: read

jobs:
pypi-publish:
name: Upload release to PyPI
verify:
name: Tests and distribution checks
runs-on: ubuntu-latest

steps:
- name: Checkout source
uses: actions/checkout@v4
Expand All @@ -19,17 +21,49 @@ jobs:
uses: actions/setup-python@v5
with:
python-version: "3.10"
cache: pip

- name: Install build dependencies
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install build
pip install -r requirements.txt -r requirements-web.txt
pip install pytest==9.1.1 build twine

- name: Build Source and Wheel distributions
run: python -m build
- name: Verify the tag matches the code version
if: startsWith(github.ref, 'refs/tags/v')
run: |
test "${GITHUB_REF_NAME#v}" = "$(python -c 'from core.version import __version__; print(__version__)')"

- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
- name: Tests
run: python -m pytest tests/ -q

- name: Build and check distributions
run: |
python -m build
twine check dist/*
python scripts/check_dist.py dist/*

- name: Keep the verified distributions
uses: actions/upload-artifact@v7
with:
user: __token__
password: ${{ secrets.PYPI_API_TOKEN }}
name: dist
path: dist/*
if-no-files-found: error

pypi-publish:
name: Upload release to PyPI
needs: [verify]
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read

steps:
- name: Download the distributions that were verified
uses: actions/download-artifact@v4
with:
name: dist
path: dist

- name: Publish to PyPI (trusted publishing, OIDC only — no API tokens)
uses: pypa/gh-action-pypi-publish@release/v1
62 changes: 22 additions & 40 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,8 @@ on:
workflow_dispatch:
inputs:
tag:
description: "Existing tag to update (example: v1.1.5)"
description: "Existing tag to update (example: v1.5.0)"
required: true
default: "v1.1.5"

permissions:
contents: write
Expand Down Expand Up @@ -83,6 +82,12 @@ jobs:
- name: Quick web import smoke test
run: python -c "from web.app import create_app; create_app()"

- name: Smoke test the built binary
shell: bash
run: |
"./dist/${{ matrix.produced_name }}" --help > /dev/null
"./dist/${{ matrix.produced_name }}" doctor

- name: Prepare artifact
shell: bash
run: |
Expand All @@ -93,7 +98,7 @@ jobs:
ls -lh "dist/${{ matrix.asset_name }}"

- name: Upload artifact
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: ${{ matrix.asset_name }}
path: dist/${{ matrix.asset_name }}
Expand All @@ -113,48 +118,25 @@ jobs:
with:
python-version: "3.10"

- name: Build deb
- name: Build the .deb
run: |
python -m pip install --upgrade pip setuptools
python scripts/build_deb.py --out dist-deb
dpkg-deb --info dist-deb/*.deb

- name: Install it with apt and run it (a broken package must not be published)
run: |
VERSION=$(python3 -c 'from core.version import __version__; print(__version__)')
DEB_NAME="stegoforge_${VERSION}_all"
BUILD_DIR="${RUNNER_TEMP}/${DEB_NAME}"

mkdir -p "${BUILD_DIR}/DEBIAN"
mkdir -p "${BUILD_DIR}/usr/share/stegoforge"
mkdir -p "${BUILD_DIR}/usr/bin"
mkdir -p "${BUILD_DIR}/usr/share/doc/stegoforge"

cp -r core detect models protocol web stegoforge.py requirements.txt requirements-web.txt version_info.txt setup.py "${BUILD_DIR}/usr/share/stegoforge/"
chmod +x "${BUILD_DIR}/usr/share/stegoforge/stegoforge.py"
ln -s /usr/share/stegoforge/stegoforge.py "${BUILD_DIR}/usr/bin/stegoforge"

cp LICENSE "${BUILD_DIR}/usr/share/doc/stegoforge/copyright"
cp README.md "${BUILD_DIR}/usr/share/doc/stegoforge/README.md"

cat <<EOF > "${BUILD_DIR}/DEBIAN/control"
Package: stegoforge
Version: ${VERSION}
Section: utils
Priority: optional
Architecture: all
Homepage: https://github.com/nour833/StegoForge
Depends: python3, python3-pip, python3-numpy, python3-scipy, python3-pil, python3-cryptography, python3-typer, python3-rich, python3-flask, python3-flask-cors, python3-pydub, python3-argon2, python3-docx, python3-openpyxl, python3-pypdf, python3-av, python3-scapy, python3-certifi, python3-requests
Maintainer: StegoForge <nourelislem84@outlook.fr>
Description: The most complete open-source steganography toolkit.
This is the native, lightweight distribution of StegoForge.
It relies on system-provided Python libraries where possible,
making it ideal for environments like Kali Linux and Debian.
EOF

dpkg-deb --build "${BUILD_DIR}"
mv "${RUNNER_TEMP}/${DEB_NAME}.deb" ./
ls -lh *.deb
sudo apt-get update
sudo apt-get install -y ./dist-deb/stegoforge_*.deb
cd "$(mktemp -d)"
stegoforge --help >/dev/null
stegoforge doctor

- name: Upload deb artifact
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: stegoforge-deb
path: "*.deb"
path: "dist-deb/*.deb"
if-no-files-found: error

release:
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -147,3 +147,4 @@ cython_debug/
# OS specific files
.DS_Store
Thumbs.db
.gstack/
5 changes: 5 additions & 0 deletions .mailmap
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Canonical identity for `git log` / `git shortlog` / `git blame` output.
# GitHub attributes commits by e-mail address; the backup branch's squashed commit used a
# placeholder (stegoforge@localhost) that matches no account, so it is mapped here.
nour833 <nourelislem84@outlook.fr> StegoForge <stegoforge@localhost>
nour833 <nourelislem84@outlook.fr> Nour833 <nourelislem84@outlook.fr>
Loading
Loading