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
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# Zero Breach

> Release v0.1.6 fixes COM file-path parsing when a folder name contains an
> executable extension, and limits historical coverage statements to evidence
> actually examined. Optional Candiru patterns still need the pinned YARA-X engine;
> Release v0.1.8 corrects the version the download reports (v0.1.7 shipped
> reporting itself as 0.1.6) and refuses network paths given to `--iocs`/`--json`
> in the single-file build. Optional Candiru patterns still need the pinned YARA-X engine;
> see [pattern validation](DETECTION-UPGRADE.md). Default checks remain standard-library-only.

[![tests](https://github.com/interplains/zero-breach/actions/workflows/tests.yml/badge.svg)](https://github.com/interplains/zero-breach/actions/workflows/tests.yml)
Expand Down Expand Up @@ -145,7 +145,7 @@ set up (e.g. a bad indicator file).

Use the official repository above and check the downloaded file before running it.

**1. Verify the publisher's signature.** Release v0.1.6 includes detached GPG
**1. Verify the publisher's signature.** Release v0.1.8 includes detached GPG
signatures for the scanner and its checksum manifest, using ArchiveGenocide's
established release key. Follow [SIGNING.md](SIGNING.md), and compare the full
key fingerprint with [ArchiveGenocide's verification page](https://archivegenocide.com/verify.html).
Expand All @@ -169,11 +169,11 @@ you do not have to trust the download at all; you can reproduce it:
```
git clone https://github.com/interplains/zero-breach.git
cd zero-breach
git checkout v0.1.6
git checkout v0.1.8
python build/build.py # writes dist/zero-breach.py and prints its SHA-256
```

These commands reproduce v0.1.6; substitute the matching tag for another release.
These commands reproduce v0.1.8; substitute the matching tag for another release.
On macOS or Linux use `python3` for the build command.
Compare the hash with that release's published hash. The development branch may
contain later changes and is not expected to reproduce an earlier release.
Expand Down
14 changes: 7 additions & 7 deletions SIGNING.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
# Verifying ArchiveGenocide release signatures

Zero Breach v0.1.7 supplies detached GPG signatures for the exact released
Zero Breach v0.1.8 supplies detached GPG signatures for the exact released
scanner and its SHA256SUMS.txt manifest. They use ArchiveGenocide's existing
Ed25519 release-signing key, also published on the archive's verification page.

Official repository: https://github.com/interplains/zero-breach
Official release: https://github.com/interplains/zero-breach/releases/tag/v0.1.7
Official release: https://github.com/interplains/zero-breach/releases/tag/v0.1.8
Independent publisher page: https://archivegenocide.com/scan/#verify-download
Established key identity: https://archivegenocide.com/verify.html

## Check the publisher and file

Download these three files from the v0.1.7 release into one directory:
Download these three files from the v0.1.8 release into one directory:

- zero-breach.py
- zero-breach.py.asc
Expand All @@ -36,9 +36,9 @@ Verification must succeed using the expected ArchiveGenocide key. A warning
that the key is not certified by your own web of trust is different from a
bad signature; do not ignore a fingerprint mismatch or failed verification.

The expected SHA-256 of zero-breach.py v0.1.7 is:
The expected SHA-256 of zero-breach.py v0.1.8 is:

9431426a940a3a9bdea617017030e0fa7b155693c3b2d25d97960d1eb262ae4e
74740af20f609f82dc6e50d66f234d0c39a20f516caa35731c2d19c11684ef44

A signature verifies which key signed these exact bytes. It does not establish
that the program has no bugs. A checksum alone is not publisher authentication.
Expand All @@ -58,14 +58,14 @@ The manifest lists the fifteen release-content files, including the optional
requirements file and Candiru rule attribution and licence records. The manifest itself and
the added public key/signatures are not self-listed.

Public verification artifacts are also retained in release-signatures/v0.1.7/.
Public verification artifacts are also retained in release-signatures/v0.1.8/.
No private key is stored in this repository or in its project backups.

## Indicator updates are a separate trust path

The manually run scan/update-iocs.py still checks HTTPS, a published SHA-256,
and indicator-file validity. It does not automatically verify GPG signatures.
The signed release manifest covers the indicator file shipped with v0.1.7;
The signed release manifest covers the indicator file shipped with v0.1.8;
it does not authenticate later files fetched from the development branch.

## Maintainer release procedure
Expand Down
19 changes: 19 additions & 0 deletions build/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,27 @@ def main(argv=None):
p = argparse.ArgumentParser(description="Rebuild all generated artifacts.")
p.add_argument("--check", action="store_true",
help="exit non-zero if any artifact changed (for CI)")
p.add_argument("--expect-version", metavar="X.Y.Z",
help="fail unless the package __version__ equals this. Run "
"with the release's own number when cutting a release, "
"so the single file can never again ship reporting a "
"different version than the tag (as v0.1.7 did).")
args = p.parse_args(argv)

if args.expect_version:
init = io.open(os.path.join(ROOT, "scan", "adint_scan", "__init__.py"),
encoding="utf-8").read()
import re as _re
found = _re.search(r'__version__\s*=\s*"([^"]+)"', init)
found = found.group(1) if found else "(unreadable)"
if found != args.expect_version:
print("\nERROR: package __version__ is {!r} but the release "
"being cut is {!r}. Update scan/adint_scan/__init__.py "
"before building.".format(found, args.expect_version),
file=sys.stderr)
return 1
print("version check: package reports {} as expected.".format(found))

if not os.path.isdir(DIST):
os.makedirs(DIST)

Expand Down
16 changes: 15 additions & 1 deletion build/bundle.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,20 @@
PKG = os.path.join(SRC, "scan", "adint_scan")
IOC = os.path.join(SRC, "iocs", "adfection_iocs.txt")

# Single source of truth for the version. Read it from the package rather than
# repeating it here: the two silently drifted once and the download shipped
# reporting the wrong version (it said 0.1.6 while it was v0.1.7). Derive it, so
# that can never happen again.
def _package_version():
init = io.open(os.path.join(PKG, "__init__.py"), encoding="utf-8").read()
m = re.search(r'__version__\s*=\s*"([^"]+)"', init)
if not m:
raise SystemExit("bundle: could not read __version__ from the package")
return m.group(1)


VERSION = _package_version()


def read(path):
return io.open(path, encoding="utf-8").read()
Expand Down Expand Up @@ -161,7 +175,7 @@ def _asof_epoch(text):
A('import sys')
A('import time')
A('')
A('__version__ = "0.1.6 (single-file build)"')
A('__version__ = "{} (single-file build)"'.format(VERSION))
A('')
A('# --- embedded indicator data ------------------------------------------------')
A('# The real file is iocs/adfection_iocs.txt in the project. Its provenance')
Expand Down
2 changes: 1 addition & 1 deletion scan/adint_scan/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@
that can be checked." It does NOT mean the device is clean. See SPEC-scanner.md.
"""

__version__ = "0.1.6"
__version__ = "0.1.8"
11 changes: 11 additions & 0 deletions scan/tests/test_bundle_release.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,3 +230,14 @@ def test_emitter_writes_separate_deterministic_withdrawal_feed(self):

if __name__ == "__main__":
unittest.main()


def test_bundle_reports_the_package_version(self):
# v0.1.7 shipped a single file that reported "0.1.6" because the version
# was hardcoded in build/bundle.py and drifted from the package. The
# bundle now derives it, so the two can never disagree again.
from adint_scan import __version__ as pkg_version
self.assertTrue(
self.bundle.__version__.startswith(pkg_version),
"bundle reports {!r}, package is {!r}".format(
self.bundle.__version__, pkg_version))
Loading