-
-
Notifications
You must be signed in to change notification settings - Fork 0
Changelog
All notable changes documented per Keep a Changelog. Versioning follows Semantic Versioning.
- Documentation coverage pass for the v1.6.1/v1.6.2 remediations: corrected the stale Ctrl+C description in TROUBLESHOOTING (previously claimed clean shutdown from anywhere), updated SECURITY.md, the wiki Security Policy, and the Architecture signal-flow diagram and prose to the context-aware SIGINT semantics, extended both developer catalogs (SignalHandler.interruptible, installer _notice/_apt_env, non-interactive operation summary), added install/update-hang and interrupt-behavior entries to both troubleshooting guides, added the interrupt semantics to the wiki Usage Guide, added a Ctrl+C FAQ entry to both FAQs, and expanded the in-application help for the install and update commands.
- Corrected a repository-breaking
.gitignorerule: a barecorepattern (intended for kernel core-dump files) matched any path component and excluded the entireapotropaios/core/package from the repository, which broke the CI consistency and documentation jobs on GitHub. The rule is now anchored (/core,core.[0-9]*), and a repository-integrity meta-test fails the suite if any critical path becomes gitignored. - Added PyYAML to the CI test-matrix, consistency, and release job installs: without it the CI meta-suite is silently excluded from pytest collection, which failed the documentation validator's suite-total check and hid the meta-checks. The validator now emits an explicit diagnostic when PyYAML is absent.
- Hardened the CodeQL workflow (explicit
build-mode: none,actions: readpermission, and a documented note on the code-scanning default-setup conflict). - Removed all 682 em-dash (U+2014) characters across 70 code, documentation, workflow, and task files per project style; the documentation validator now scans the entire repository for the character and fails when one is present.
- README badges: replaced the static mypy and CI badges with dynamic ones, and added dynamic pytest, coverage, and framework-version badges. Workflow-status badges read the CI, Security, CodeQL, and Docs Validate pipelines; mypy, test-count, and coverage badges read shields endpoint JSON published to a
badgesbranch by a new CI job; the version badge readspyproject.tomldirectly.
- Ctrl+C during an interactive-menu operation now aborts the operation and returns control to the menu instead of terminating the application.
SignalHandlergains a nestableinterruptible()scope: while active, SIGINT raises KeyboardInterrupt into the running frame; the menu session runs inside the scope, the main loop recovers to the main menu, and the Install & Update submenu recovers in place. Outside the scope (headless CLI), SIGINT semantics are unchanged (cleanup, exit 130), and SIGTERM/SIGHUP always terminate regardless of scope.
- Install and update operations no longer hang. Every package-manager invocation closes stdin (
stdin=DEVNULL), so residual prompts that-y/--noconfirmdo not cover -- classically dpkg configuration-file questions, which rendered invisibly because output is captured -- receive EOF instead of blocking indefinitely. apt operations additionally carryDpkg::Options::=--force-confdef/--force-confold(answering conffile prompts with the safe default),DPkg::Lock::Timeout=60(bounding waits on a concurrent package manager), andAPT_LISTCHANGES_FRONTEND=none. - The apt update path uses
apt-get install --only-upgrade <pkg>; the previousapt-get upgrade <pkg>form does not upgrade a single named package. - Install and update now print an operator-visible status line before the operation starts (package, manager, timeout, abort hint); previously the only feedback was an info-level log below the console threshold, so a legitimately long operation was indistinguishable from a hang.
-
APOTROPAIOS_BASE_DIRenvironment variable overrides the framework base directory when it names an existing directory, relocating the data tree (logs, rules, backups) and the project-local configuration lookup; an invalid override warns and falls back to the default.
- Integration CLI tests run every subprocess invocation inside an isolated temporary base directory via the new override; suite runs no longer write log files into the repository data tree. Added regression tests proving the redirection and the invalid-override fallback (suite total 305 → 307).
- Completed the required header components (Project, Description) on twelve test files that carried abbreviated headers.
-
test_versioncompares the CLI output against the canonical framework version instead of falling back to a stale hardcoded literal.
- CodeQL workflow upgraded from
github/codeql-action@v3to@v4(Node.js 24 runtime; v3 enters deprecation December 2026 and already emits workflow warnings). - All workflows upgraded from
actions/setup-python@v5(Node.js 20) to@v6(Node.js 24); GitHub forces Node.js 24 by default from June 2026 and removes Node.js 20 from runners in September 2026. - The CI meta-test pin standard now enforces
setup-python@v6andcodeql-action/init|analyze@v4in addition to the existing checkout/artifact pins, so future drift on these actions fails the suite.
- Bug-report issue form: the operating-system dropdown now lists the framework's actual supported platforms (Ubuntu, Kali Linux, Debian 12, Rocky Linux 9, AlmaLinux 9, Arch Linux) instead of RHEL/CentOS Stream/Fedora, which the framework does not list as supported platforms.
- Bug-report issue form: the security-policy link is absolute; the previous relative link resolved incorrectly when the form rendered from the issue-chooser path.
- Added programmatic documentation verification (
scripts/validate_docs.py): no version identifiers outside changelogs, per-file and total line counts against source, suite-total test counts against the collected pytest suite, CLI command counts againstCLI_COMMANDS, and internal wiki/relative link resolution. - Corrected all stale suite-total test-count references from 230 to 305 across README, DEVELOPMENT_GUIDE, CONTRIBUTING, SETUP_GUIDE, FAQ, and wiki pages; updated the test-tree structure (unit 11 files/253 tests, added the CI tier of 26 tests, "three tiers" to "four tiers"). Regenerated per-file and total line counts (total 14,415); removed a residual version callout from the README tagline.
- Documented the CI/CD pipelines, validation scripts, and repository templates in DEVELOPMENT_GUIDE and the wiki Contributing page.
- Expanded the automated test suite from 230 to 305 tests. New
tests/unit/test_backend_validation.py(30 tests) exercises the backend command-composition re-validation, ufw fail-closed action mapping, ipset per-set-type entry validation, and firewalld IPv6 family derivation directly, closing the mock-backend coverage gap flagged at audit close. Newtests/unit/test_audit_regressions.py(19 tests) locks in the configuration trust gate, RHEL-family package mapping, strict IPv6/CIDR validation, the retry guard, ErrorContext traceback capture, and log extra_context sanitization. Newtests/ci/test_workflows.py(26 tests) validates the workflow and issue-template files as structured data. - Added
scripts/check_version_consistency.pyverifying every version-bearing file against the canonicalVERSION.
- Corrected the existing test suite: removed 27 unused imports and a dead local across the test tree; added a missing assertion in the rule-export test; converted backend side-effect registration imports to explicit
importlibloops in the tests and incli.py; made the package__version__export explicit. The entire tree (package, scripts, tests) is now pyflakes-clean with no exceptions, and mypy --strict passes on all 37 files.
- Expanded the CI pipeline (
ci.yml) with a pyflakes lint job, pip caching, run concurrency, a coverage-artifact upload on the test matrix, and a repository-consistency job running the version and documentation validators. - Enhanced the release pipeline (
release.yml) with a tag-versus-canonical-version gate, a unifiedSHA256SUMS.txt, and build-artifact upload. - Added
security.yml(bandit SAST with a weekly schedule and a medium+/medium+ gate),codeql.yml(CodeQL semantic analysis for Python), anddocs-validate.yml(runs the validation scripts and the CI meta-suite on documentation/source/template/workflow changes).
- Standardized workflow action pins on
actions/checkout@v6,actions/upload-artifact@v6, andactions/download-artifact@v6, and corrected stale workflow header versions.
- Added the full issue-template set under
.github/ISSUE_TEMPLATE/: structured Bug Report, Feature Request, Documentation Issue, and Platform / Backend Support Request forms with required fields and backend/OS dropdowns, plusconfig.ymldisabling blank issues and routing security disclosures to the security policy and questions to the FAQ wiki. - Added
.github/PULL_REQUEST_TEMPLATE.mdwith change categorization, a verification-evidence checklist, and the project quality gates.
- Removed version identifiers from all documentation outside the changelogs (README badge and version-history table, SECURITY supported-versions table, SETUP_GUIDE download examples, DEVELOPER_GUIDE/DEVELOPMENT_GUIDE/FAQ, wiki sidebar, footer, and guides); documentation is now fact-of.
- Corrected the Configuration-Reference and SETUP_GUIDE statements that no external configuration file exists; documented the configuration file search order, trust gate, and precedence.
- Documented
--non-interactivedestructive-command semantics, expanded backup-contents description, and regenerated all per-file and total line counts from source.
- CI and release workflows pin
actions/checkout@v6per the project workflow standard.
- cli.py header now states 21 subcommands and lists all of them (previously claimed 20 and listed 17); parser docstring corrected from 17 to 21.
- menu/main.py header describes the 8-option main menu (previously "7-category").
- os_detect
_determine_familydocstring no longer claimsOS_FAMILY_MAPusage; it documents the local map, ID_LIKE fallback, and binary probing actually implemented. -
export_rulesreturns the number of rules actually written to the export file; expired and unreadable records are no longer counted.
- Removed 45 unused imports across 18 modules and one unused local variable; removed f-string prefixes from placeholder-less literals.
- Normalized over-indented docstring bodies across the nftables, firewalld, ufw, and ipset backends.
-
retry()rejectsmax_retries < 1with an explicit ValueError and replaces the exhaustion assert with an explicit raise (asserts are stripped under-O). -
ErrorContext.recordcaptures tracebacks withtraceback.format_exception(exc)so recording outside an except block no longer stores "NoneType: None". -
die()is typed-> NoReturn; removed the dead control-character pass insanitize_input(the whitelist already excludes them); removed the duplicate result-list initialization inparallel_exec; repaired the broken sentence in the common.py header.
- Privileged executions now run with a hardened fixed
PATHset during security initialization, closing the untrusted-search-path window for subprocess resolution of firewall binaries under root.
- Detection running-state checks execute through the already-resolved binary path instead of performing a second PATH lookup; removed the dead ipset kernel-module fallback branch.
- Backend service operations no longer report success unconditionally: iptables, nftables, firewalld, and ufw
enable/disablenow check systemctl/ufw results; iptables and ufwblock_all,allow_all, andresetverify every policy and flush command; firewalldblock_allverifies panic mode and its drop-zone fallback,allow_allverifies the zone reset, andsaveverifies--runtime-to-permanent; the firewalld restore path verifies--reload. Failed emergency controls now return failure instead of claiming success.
- nftables rules with IPv6 operands now emit
ip6 saddr/ip6 daddr(previouslyip saddr, a syntax error for IPv6 addresses). - firewalld rich rules derive the address family from the operands (
ipv6when IPv6 addresses are present) instead of hardcodingipv4; mixed-family rules are rejected explicitly. - firewalld protocol-only ICMP rules use
protocol value="icmp|icmpv6"; the invalidicmp-block-inversionrich-rule element (a zone option) is no longer emitted.
- Menu-driven backups and restores now include the rule index and state files: the backup subsystem records the rules directory at initialization and both
create_backupandrestore_backupfall back to it when no explicit directory is passed, unifying the CLI and menu paths.
- The shipped configuration file is now loaded at startup (previously parsed by nothing). Search order:
/etc/apotropaios/apotropaios.conf,<base>/conf/apotropaios.conf, packaged default. Every candidate passes a trust gate -- owned by root or the effective user, not group/world-writable -- before use. Precedence: command line > configuration file > built-in defaults. The file drives the console log level and default backend selection.
-
--non-interactiveis now honored: destructive confirmations (reset) proceed without prompting in non-interactive mode instead of blocking on stdin. - The reset confirmation reads from the controlling terminal when available, so piped stdin cannot accidentally satisfy it; falls back to stdin only when no terminal exists.
- The minimum Python version is enforced at CLI entry with a clear error and non-zero exit (previously logged as critical and execution continued).
-
FrameworkLogger.init()shuts down any previous session before re-initializing, eliminating a file-descriptor leak where the prior stdlib logger stayed registered with its rotating file handler attached. - The log entry counter is guarded by a lock; the expiry monitor daemon thread and the main thread previously raced a non-atomic increment.
- Deactivating a rule preserves its duration type and TTL in the state tracker; temporary rules no longer silently become permanent on deactivation, and expired rules are recorded as
expiredin both the index and the state tracker. -
rule_createrestores the session's original backend after a per-rule backend switch, matching removal, deactivation, and activation semantics; a failed application also restores the original backend.
-
validate_cidrno longer swallows its own prefix-out-of-range error inside the version-detection try block; out-of-range prefixes are reported specifically instead of as an invalid IP portion. - IPv6 validation performs exact semantic validation via the stdlib
ipaddressmodule after the pattern check; malformed forms such as:::and1::2::3are now rejected.
- ufw backup/restore round-trip: backups now capture the complete
/etc/ufwconfiguration directory (ufw_etcin the archive) alongside the human-readable status dump; restore reports failure honestly when an archive contains no restorable ufw configuration instead of logging success while restoring nothing.
- Package install and update operations use the long operation timeout (300 s) instead of the 30-second subprocess default, which real package operations routinely exceed;
subprocess.TimeoutExpiredis caught and reported as an installation error instead of escaping as a raw exception.
-
os_detectID_LIKE family fallback maps therhelfamily to package managerdnf(previously the invalid valuerhel), restoring install/update support on RHEL-derivative distributions.
-
CleanupStackuses a reentrant lock: the signal handler executes the cleanup stack in the main thread, and a non-reentrant lock deadlocked the process when a signal interrupted a frame already holding the lock (CWE-833). -
FileLock.acquirere-opens the lock file on every retry iteration; after stale-lock removal and recreation, flock was previously taken on the unlinked inode, allowing two simultaneous holders (CWE-367). Removed the redundantBlockingIOErrorfrom the except clause.
- The log sanitizing filter now masks the structured
extra_contextfield in addition to the message. Previously extra_context reached the file formatter verbatim, permitting log-line forging via control characters (CWE-117) and secret leakage through structured fields.
- ufw action mapping fails closed: unknown or unsupported actions (masquerade, snat, dnat, return, or unrecognized values) raise
RuleApplyErrorinstead of silently translating toallow; compound actions without a supported terminal no longer default to a permissive verb except for explicit log-only rules.
- ipset
block_allfails closed: coverage is achieved with the two complementary/1networks (hash:netrejects a/0prefix, so the previous0.0.0.0/0add always failed), and every set and iptables operation is verified -- the emergency control returns failure when blocking did not fully apply instead of claiming success while blocking nothing.allow_allverifies block-set removal.
- Completed defense-in-depth re-validation across all five backends: nftables re-validates
log_prefix,log_level,limit,limit_burst,handle,table,table_family, andchainbefore interpolation into nft command strings (closing quote-breakout/statement-injection paths); firewalld re-validates rich-rulelog_prefix/log_level/limitand removal-path ports; iptables re-validatesconn_state,limit,limit_burst,log_prefix, andlog_level; ufw re-validates removal-path rule numbers, ports, and protocols; ipset validates entries for every set type includinghash:*,port,hash:net,iface, andlist:set.
First production release of the Apotropaios Firewall Manager Python variant. Designates feature-complete, fully audited, stable codebase after 12 development iterations spanning 6 distinct audit passes.
Codebase: 35 Python files, 13,877 lines, 430 callable entities, stdlib-only. Quality: mypy --strict zero errors, 230 automated tests, zero known bugs. Security: 6 CWE-mapped vulnerabilities found and fixed across 3 audit passes. Coverage: 21 CLI commands, 20 help pages, 5 firewall backends, 6 Linux platforms.
Audit history:
- v0.1.5: Line-by-line security audit -- 6 bugs fixed (SIGHUP, firewalld backup, timing side-channel, duplicate actions, ExpiryMonitor, hierarchy comment)
- v0.1.6: Code quality audit -- 44 docstrings added, 3 unused imports removed
- v0.1.7: Technical quality audit -- 16 findings (allow_all flush, backend switch failure, dead parameter, unchecked return codes, Color TTY, conn_state validation)
- v0.1.8: CLI expansion -- enable/disable/reset commands, root privilege check, IPTABLES_TABLES validation, ErrorContext traceback, empty field standardization
- v0.1.9: Secure code audit -- tarfile extraction hardening (CWE-22/59), log injection prevention (CWE-117), standalone exception wrapping (CWE-209)
- v0.1.10: SAST analysis -- limit_burst/log_level taint gaps closed (CWE-20), swallowed index.add exceptions replaced with error logging
- v0.1.11: Documentation audit -- 26 discrepancies corrected, version history restructured with per-category version entries
Discrepancies fixed (26):
- Total line count updated (13,853 → 13,877) across 6 files
-
logging.pyline count updated (727 → 729) across 4 files -
engine.pyline count updated (562 → 576) across 4 files -
restore.pyline count updated (255 → 263) across 4 files - CLI command count updated (18 → 21) across 6 files
- Version badge updated from 0.1.8-dev to current in README
- Architecture layer subtotals recalculated (L1: 4,809, L4: 2,520)
Version history restructured:
- Split bundled v0.1.9-dev into v0.1.9 (security audit) + v0.1.10 (SAST)
- Each audit category now has its own version entry
-
SAST-001:
limit_burstparameter passed from CLI through engine to iptables/nftables subprocess with zero validation at any layer. Addedvalidate_numeric()call in engine validation chain. (CWE-20) -
SAST-002:
log_level(syslog level for LOG action) passed to backends unvalidated despitevalidate_syslog_level()existing. Added validation call in engine. (CWE-20) -
SAST-003:
rule_block_all()andrule_allow_all()silently swallowedrule_index.add()failures viaexcept Exception: pass. Rules applied to firewall but not tracked in index -- data integrity gap. Added error logging.
Line-by-line secure code audit of all 35 source files focused on CWE Top 25, OWASP CRG, and NIST SSDF security requirements.
-
S001:
restore.pytarfile extraction vulnerable to symlink, hard link, and device node attacks. Added explicit checks forissym(),islnk(),isdev(),ischr(),isblk()members, plus Python 3.12+filter="data"parameter for defense-in-depth. (CWE-22, CWE-59) -
S004:
LogSanitizercontrol character pattern allowed newline (\x0a) and carriage return (\x0d) through, enabling log injection attacks where attacker-controlled data could forge fake log entries. Fixed pattern to strip all control chars except tab. (CWE-117) -
S005:
apotropaios.pystandalone execution lacked the exception handling wrapper present in__main__.py. Unhandled exceptions exposed full Python tracebacks. Added KeyboardInterrupt handler and catch-all.
New CLI commands (3):
-
enable-- Start and enable the active firewall backend via systemctl. -
disable-- Stop the active firewall backend. -
reset-- Reset the active backend to defaults with interactive confirmation.
Root privilege detection:
- Added root privilege check to
_initialize(). Logs warning when not running as root so users see early feedback before firewall operations fail.
Deferred audit findings fixed (5):
-
Q006:
ErrorContext.get_formatted()now includes traceback tail. Addedget_traceback()method for full traceback access. -
Q011: iptables backend validates
--tableagainstIPTABLES_TABLES. - Q013: Empty rule fields stored as empty strings (not "any") in index.
-
Q014:
ttl.isdigit()replaced with ASCII-only check. -
CLI_COMMANDSexpanded from 18 → 21, help system 17 → 20 registrations, dispatch 17 → 20 handlers.
Line-by-line technical code quality audit of all 35 source files identified and fixed 16 quality issues across 2 critical, 5 high, and 9 medium severity findings.
Critical fixes:
-
Q010:
iptables.allow_all()set ACCEPT policies without flushing existing DROP/REJECT rules -- traffic remained blocked despite "allow all" success. Fixed: flush filter table before setting policies. -
Q015:
rule_remove(),rule_deactivate(), andrule_activate()silently swallowed backend switch failures (except: pass), causing operations to execute against the wrong firewall backend. Fixed: track switch state explicitly, log failures instead of silently ignoring.
High fixes:
-
Q001: constants.py comment referenced nonexistent
validate_sanitize_input(). - Q004: Duplicate comment line in errors.py signal handler section.
-
Q007:
iptables._run()acceptedcheckparameter but never passed it tosubprocess.run(). Wired through. -
Q008:
iptables.enable()/disable()ignored systemctl return codes. Added returncode checking with warning log on failure. -
Q012:
nftables.pyusedconn_statedirectly in nft expressions without callingvalidate_conn_state(). Added defense-in-depth validation.
Medium fixes:
-
Q002:
LogLevel.to_stdlib_level()created mapping dict on every call. Moved to module-level constant_LOGLEVEL_TO_STDLIB. -
Q003:
_Colorcheckedsys.stdout.isatty()but all output goes to stderr. Changed to checksys.stderr.isatty(). -
Q009: Status message showed
pythoninstead ofpython3(PY-005 regression). -
Q016:
parse_iso_timestampimported inside loop body inrule_check_expired(). Moved to function top.
Documented (deferred to next sprint):
- Q005: Inconsistent exception context passing pattern (works correctly, cosmetic).
- Q006:
ErrorContext._tracebackstored but not exposed inget_formatted(). - Q011:
validate_table()too permissive for iptables (accepts any alphanumeric). - Q013: Empty fields stored as "any" in rule index (inconsistent with input).
- Q014:
ttl.isdigit()accepts Unicode digits (upstream regex prevents issue).
Missing CLI commands identified (future sprint):
-
enable,disable,reset-- available in interactive menu but not CLI mode. -
save,load-- backend dispatch functions exist but unwired.
Complete line-by-line technical code quality audit of all 35 source files. Focused on annotations, docstrings, unused imports, resource management, thread safety, return type consistency, and code organization.
Docstring completeness (44 additions):
- Added docstrings to all 12 public methods on
NftablesBackend(nftables.py) - Added docstrings to all 10 public methods on
FirewalldBackend(firewalld.py) - Added docstrings to all 12 public methods on
UfwBackend(ufw.py) - Added docstrings to 9 public methods on
IpsetBackend(ipset.py) - Added docstring to
RuleState.initializedproperty (state.py)
Unused import removal (3 removals):
- Removed
import shutilfromipset.py(unused after prior refactor) - Removed
import shutilfromufw.py(unused after prior refactor) - Removed
import sysfromindex.py(unused)
Quality verification (13 automated checks, all passing):
- Type annotations: complete on all public functions and methods
- Docstrings: present on all public classes, functions, and methods
- No bare
exceptclauses anywhere in codebase - No TODO/FIXME/HACK markers in production code
- All module header versions consistent (0.1.8-dev)
- All
open()calls use context managers (withstatement) - No O(n²) string concatenation in hot paths
- Thread-safe lock usage in all singleton mutating methods
- All backend methods have return type annotations
- All
__init__.pypackage comments accurate - Help system registrations match CLI_COMMANDS exactly (17/17)
- All resource handles properly managed
- Lesson references in code comments are consistent
Complete line-by-line secure code audit identified and fixed 6 bugs:
-
BUG-001: SIGHUP exit code was 80 instead of Unix standard 129 (128+1). Added
signal.SIGHUP: 129to_SIGNAL_EXIT_CODES. -
BUG-002: Exception hierarchy comment showed wrong inheritance for RestoreError/BackupNotFoundError. Fixed to match actual
ApotropaiosErrorparent. -
BUG-003: Firewalld backup/restore functionally broken -- backup saved text dump, restore just reloaded. Fixed: saves/restores zone XML files from
/etc/firewalld/zones/. - BUG-004: ExpiryMonitor threshold documented inconsistently. Standardized to 10 minutes (600 seconds).
-
BUG-005:
validate_rule_action()allowed duplicate components ("log,log"). Added duplicate detection. -
BUG-006:
verify_checksum()used!=instead ofhmac.compare_digest()-- timing side-channel (CWE-208). Fixed with constant-time comparison.
- Added
encoding="utf-8"to 9open()calls missing explicit encoding - Version string deduplicated:
__init__.pyimports fromconstants.py -
DEFAULT_LOG_LEVELchanged fromINFOtoWARNING -
menu/__init__.pydocstring corrected from "7-category" to "8-option" - Fixed 7 documentation inaccuracies (exception count, keywords, hierarchy, log levels, help count, mermaid, backend order)
Complete rewrite of all 27 documentation files. Makefile expanded to 684 lines/56 targets.
-
PY-007: Post-shutdown console noise --
_shut_downflag -
PY-008:
statusshowed rules instead of service state - PY-009: pip3 install build failure -- explicit package discovery
- Menu: System Info, Help submenu, Rule Management, Quick Actions improvements
- Position-independent
--log-leveland--backendflags - Console default WARNING,
helpcommand,import --dry-run
- PY-003: 16/17 CLI handlers were dead stubs -- all 18 rewired
- PY-004: Tests passed despite dead code -- subprocess integration tests
-
PY-005:
pip→pip3 -
PY-006: No standalone execution -- created
apotropaios.py
Python 3.12+ rewrite targeting bash v1.1.10 parity. 35 files, stdlib-only, mypy --strict zero errors, 230 tests.
Apotropaios Firewall Manager -- Python Variant · Home · Quick Start · Architecture · Security · Bash Variant
Apotropaios -- Python Variant
Getting Started
Architecture
Operations
Project
35 files · 14,545 lines · 322 tests