Skip to content

feat(gophish): add Gophish phishing campaign injector (#311) - #321

Open
SamuelHassine wants to merge 7 commits into
mainfrom
feat/injector-gophish
Open

feat(gophish): add Gophish phishing campaign injector (#311)#321
SamuelHassine wants to merge 7 commits into
mainfrom
feat/injector-gophish

Conversation

@SamuelHassine

@SamuelHassine SamuelHassine commented Jul 14, 2026

Copy link
Copy Markdown
Member

Summary

  • Add the Gophish phishing injector that creates and launches a campaign via the Gophish REST API and reports the campaign id and open / click / submit stats as human-response.
  • Wire CI: add gophish to the CircleCI build_docker_images, publish_images and test matrices.

Notes

  • Gophish server connection is injector config; per-campaign parameters are contract fields.
  • Template, landing page, sending profile and target group are referenced by name, which is the documented Gophish API form (Gophish resolves those objects by name, so no id lookup is needed).
  • For a zero-dependency alternative see the native phishing injector.
  • Icon placeholder to be finalized per the icon standard (Connector icons should be square with a solid background (netexec, teams, ...) #305). The injector loads the icon defensively and starts without one until the real asset lands, instead of crashing.
  • CI matrix lines are shared across sibling PRs; a trivial rebase may be needed.

Review fixes

  • Startup robustness: a missing placeholder PNG no longer raises FileNotFoundError at startup (logs a warning and registers without an icon).
  • Input validation: mandatory campaign contract fields are validated up front; a clear error is returned instead of forwarding None values to the Gophish API.
  • Contract guard: process_message validates injector_contract_id and fails with a clear "unsupported contract" error before creating a campaign, so a misrouted or stale-contract injection no longer reaches the Gophish API.
  • Output shape: execution_output_structured is a plain JSON object (campaign_id, stats) instead of double-encoded JSON wrapped in one-element lists.
  • Dockerfile shell portability: the client-python override guard uses a POSIX [ -n "..." ] test (quoted) so it evaluates correctly under the BusyBox /bin/sh in python:3.13-alpine instead of the bash-only [[ ... ]] form.
  • Secure TLS default: verify_tls now defaults to true in GophishConfig and the client. The opt-out for self-signed / local servers is documented in .env.sample and config.yml.sample. This avoids exposing the API key on untrusted networks.
  • Explicit launch date: campaign creation always sends an ISO 8601 UTC launch_date (defaults to now) for an unambiguous immediate launch instead of relying on the server clock.
  • Diagnosability: the process_message exception path now logs the failure at error level with the inject id (per the common-implementation logging guidance), and client HTTP errors surface the trimmed Gophish response body (missing template / page / group, permission or 404 details) in the returned message and logs, instead of only the status line. The previously unused client logger is now wired to these error paths.

Test plan

  • isort (repo-root profile) / black / flake8 clean
  • python -m unittest (contracts + client + injector, requests mocked) - 14 tests
  • docker build with --build-context injector_common (CircleCI build_docker_images-gophish)

Closes #311

Launches phishing campaigns through a Gophish server and reports open/click/submit stats as human-response expectations. Wires the injector into the CircleCI build, publish and test matrices.
Copilot AI review requested due to automatic review settings July 14, 2026 09:32

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new gophish/ injector module to the OpenAEV injectors monorepo, implementing a thin Gophish REST API client + OpenAEV injector entrypoint + contract definition, and wires the injector into the CircleCI test/build/publish matrices.

Changes:

  • Introduce the gophish_injector package (config loader, contracts, REST client, injector entrypoint) plus local dev artifacts (Dockerfile, compose, samples).
  • Add a minimal unit test suite for the Gophish client and contract builder.
  • Update CircleCI matrices to include gophish in test, build_docker_images, and publish_images.

Reviewed changes

Copilot reviewed 18 out of 21 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
gophish/test/test_gophish.py Adds unit tests for contract creation and basic REST client behavior (requests mocked).
gophish/test/init.py Marks test directory as a package.
gophish/README.md Documents injector purpose, usage/development, and icon requirements.
gophish/pyproject.toml Defines packaging/dependencies and cmw metadata for the new injector.
gophish/manifest-metadata.json Adds OpenAEV integration catalog metadata for the Gophish injector.
gophish/gophish_injector/openaev_gophish.py Implements the injector entrypoint that creates/launches campaigns and reports results back to OpenAEV.
gophish/gophish_injector/img/README.md Documents the expected icon asset placement/standard.
gophish/gophish_injector/contracts_gophish.py Adds the Gophish campaign contract (fields + expectations).
gophish/gophish_injector/configuration/injector_config_override.py Defines injector-level config overrides (id/name/icon filepath).
gophish/gophish_injector/configuration/gophish_config.py Adds Gophish connection settings (base_url/api_key/verify_tls).
gophish/gophish_injector/configuration/config_loader.py Wires OpenAEV + injector + gophish configs into a daemon Configuration.
gophish/gophish_injector/configuration/init.py Marks configuration directory as a package.
gophish/gophish_injector/client/gophish_client.py Implements a thin REST client for creating campaigns and fetching stats.
gophish/gophish_injector/client/init.py Marks client directory as a package.
gophish/gophish_injector/init.py Defines the package version marker.
gophish/Dockerfile Adds multi-stage Docker build for the injector, including optional client-python override.
gophish/docker-compose.yml Adds a compose service definition for running the injector container.
gophish/config.yml.sample Adds a sample YAML config for local/dev runs.
gophish/.env.sample Adds environment-variable sample values for Docker/compose usage.
gophish/.dockerignore Adds docker ignore rules for local configs and caches.
.circleci/config.yml Adds gophish to CircleCI test/build/publish matrices.

Comment thread gophish/gophish_injector/openaev_gophish.py
Comment thread gophish/gophish_injector/openaev_gophish.py
Comment thread gophish/gophish_injector/openaev_gophish.py
Adds coverage for __init__, process_message and start to satisfy the codecov patch gate.
@codecov

codecov Bot commented Jul 14, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 95.00000% with 8 lines in your changes missing coverage. Please review.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
gophish/gophish_injector/client/gophish_client.py 89.83% 6 Missing ⚠️
gophish/gophish_injector/openaev_gophish.py 96.49% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #321      +/-   ##
==========================================
+ Coverage   69.27%   70.39%   +1.12%     
==========================================
  Files          81       88       +7     
  Lines        3505     3665     +160     
==========================================
+ Hits         2428     2580     +152     
- Misses       1077     1085       +8     
Files with missing lines Coverage Δ
gophish/gophish_injector/__init__.py 100.00% <100.00%> (ø)
...sh/gophish_injector/configuration/config_loader.py 100.00% <100.00%> (ø)
...h/gophish_injector/configuration/gophish_config.py 100.00% <100.00%> (ø)
...injector/configuration/injector_config_override.py 100.00% <100.00%> (ø)
gophish/gophish_injector/contracts_gophish.py 100.00% <100.00%> (ø)
gophish/gophish_injector/openaev_gophish.py 96.49% <96.49%> (ø)
gophish/gophish_injector/client/gophish_client.py 89.83% <89.83%> (ø)

📢 Thoughts on this report? Let us know!

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

- Load the injector icon defensively so a missing placeholder PNG no longer
  raises FileNotFoundError at startup (real artwork tracked in #305); log a
  warning and register without an icon instead.
- Validate the mandatory campaign contract fields before calling the Gophish
  API and fail with a clear message listing the missing fields, rather than
  silently forwarding None values.
- Emit execution_output_structured as a plain JSON object (campaign_id and
  stats) instead of double-encoded JSON wrapped in one-element lists, matching
  the sibling injector convention.
- Extend tests to cover the missing-field validation, the flattened structured
  output shape, and the graceful missing-icon fallback.
@SamuelHassine

Copy link
Copy Markdown
Member Author

Review and fix summary

Reviewed the full changed files end to end (not just the diff) against sibling injector conventions (ai-redteam, netexec, nmap, teams, phishing) and injector_common, then applied fixes in f9f1aa1.

Fixes applied

  • Startup robustness (must-fix): the icon is now loaded via a defensive _load_icon() helper. A missing placeholder PNG returns empty bytes and the injector registers without an icon (logging a warning) instead of raising FileNotFoundError at startup. Real 512x512 opaque artwork is still tracked in Connector icons should be square with a solid background (netexec, teams, ...) #305.
  • Input validation (should-fix): process_message validates every mandatory campaign contract field before calling the Gophish API and raises a clear ValueError listing the missing fields, so no None values reach the payload.
  • Output consistency (should-fix): execution_output_structured is now a plain JSON object ({"campaign_id": <int>, "stats": {...}}) instead of double-encoding stats and wrapping values in one-element lists, matching the sibling convention.
  • Tests (should-fix): added coverage for the missing-field validation, the flattened structured-output shape, and the graceful missing-icon fallback. 9 tests, all passing.

Verification

  • isort (repo-root profile), black and flake8 all clean on the touched files.
  • python -m unittest green (9 tests).
  • Full CI green: GitHub Actions (Test gophish + siblings, signed commits, linked issue, PR title), CircleCI (ensure_formatting, linter, test-, build_docker_images-), codecov/patch and codecov/project.

Follow-ups (non-blocking, out of scope here)

  • Stats are read at campaign-creation time only (open/click/submit are 0 at launch); wiring get_campaign_stats polling for progressive human-response reporting can be a later change.
  • Defining ContractOutputElement outputs so campaign_id/stats map to typed UI outputs would complement the structured payload.

Blocker

  • Formal GitHub approval could not be recorded: the review is running under the PR author's account (SamuelHassine), and GitHub does not allow approving your own PR. A different maintainer needs to click Approve. mergeStateStatus is BLOCKED solely on REVIEW_REQUIRED; everything else is green.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 19 out of 22 changed files in this pull request and generated 1 comment.

Comment thread gophish/Dockerfile Outdated
The client-python override block used the bash-specific [[ ... ]] test, but
the image is python:3.13-alpine whose /bin/sh is BusyBox ash, so the
PYOAEV_GIT_BRANCH_OVERRIDE branch could be silently skipped. Switch to a
POSIX [ -n "..." ] test with the variable quoted.

Also expose the existing GOPHISH_VERIFY_TLS setting in .env.sample and
docker-compose.yml so the TLS-verification option is configurable via the
documented deployment path.
@SamuelHassine

Copy link
Copy Markdown
Member Author

Second-pass review and fix summary

Re-ran the full review workflow after new review comments landed. Re-read every changed file end to end (not just the diff) and cross-checked against sibling injectors (nuclei, nmap, shodan, http-query) and injector_common.

New review comment addressed

  • Dockerfile shell portability (must-fix): the client-python override guard used the bash-specific [[ ... ]] test, but the image is python:3.13-alpine whose /bin/sh is BusyBox ash, so the PYOAEV_GIT_BRANCH_OVERRIDE block could be silently skipped when the build-arg was provided. Replaced with a POSIX [ -n "${PYOAEV_GIT_BRANCH_OVERRIDE}" ] test with the variable quoted. Fixed in 28c7ebc.

Additional finding from the independent re-review

  • Config surface completeness (should-fix): GophishConfig.verify_tls existed but was not exposed in .env.sample or docker-compose.yml, so operators had no documented way to toggle TLS verification. Added GOPHISH_VERIFY_TLS to both.

Verified good (no change needed)

  • Icon defensive load, mandatory-field validation and the flattened execution_output_structured object (from the first pass) all match sibling conventions - execution_output_structured uses json.dumps({...}) exactly like nmap/nuclei.
  • Contract fields, config-loader wiring, client error handling and tests read correctly.

Verification

  • Full CI green: GitHub Actions (Test gophish + siblings, signed commits, linked issue, PR title), CircleCI (ensure_formatting, linter, test-*), codecov/patch and codecov/project.
  • All 5 commits signed (%G? = G).
  • 0 unresolved review threads.

Follow-ups (non-blocking, out of scope here)

  • Stats are read at campaign-creation time only (open/click/submit are 0 at launch); wiring get_campaign_stats polling for progressive reporting can be a later change.
  • Sibling injectors still use the bash-only [[ ... ]] Dockerfile guard; aligning them is a separate cleanup.

Blocker

  • Formal GitHub approval could not be recorded: the review runs under the PR author's account (SamuelHassine) and GitHub does not allow approving your own PR. A different maintainer must click Approve. mergeStateStatus is BLOCKED solely on REVIEW_REQUIRED; everything else is green.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 19 out of 22 changed files in this pull request and generated 2 comments.

Comment thread gophish/gophish_injector/openaev_gophish.py
Comment thread gophish/gophish_injector/configuration/gophish_config.py

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 19 out of 22 changed files in this pull request and generated 2 comments.

Comment thread gophish/gophish_injector/client/gophish_client.py
Comment thread gophish/gophish_injector/configuration/gophish_config.py
#311)

- Default verify_tls to true (secure by default) in GophishConfig and the
  client, documenting the opt-out for self-signed / local servers in
  .env.sample and config.yml.sample. Skipping TLS verification exposed the
  API key to interception.
- Validate injector_contract_id in process_message and fail with a clear
  "unsupported contract" error before calling the Gophish API, so a
  misrouted or stale-contract injection no longer produces confusing
  campaign-creation failures.
- Send an explicit ISO 8601 UTC launch_date on campaign creation (defaults
  to now) instead of relying on the server clock for immediate launch.
- Add tests for the unsupported-contract path, the launch_date payload and
  the secure verify_tls default.
@SamuelHassine

Copy link
Copy Markdown
Member Author

Review and fix summary (final pass)

Re-ran the full end-to-end review: re-read every changed file (not just the diff) against sibling injectors (nuclei, nmap, shodan, http-query, teams) and injector_common, addressed all open review comments, and applied the fixes in b69f981.

Review threads addressed (all resolved)

  • Contract guard (must-fix): process_message now validates injector_contract_id via DataHelpers.get_injector_contract_id and raises a clear "unsupported contract" error before calling the Gophish API. A misrouted or stale-contract injection is reported back as an ERROR execution.
  • Secure TLS default (must-fix): verify_tls now defaults to true in GophishConfig and the client. The opt-out for self-signed / local servers is documented in .env.sample and config.yml.sample. This removes the insecure-by-default certificate skip that could expose the API key.
  • Explicit launch date (should-fix): campaign creation always sends an ISO 8601 UTC launch_date (defaults to now) for an unambiguous immediate launch. On the name-vs-id point, name references are kept intentionally: the documented Gophish API resolves template/page/smtp/groups by name, so no id lookup is required; the 400 risk the reviewer flagged came from the missing launch_date, which is now provided.

Tests

  • 11 tests, all passing locally (isort/black/flake8 clean). Added coverage for the unsupported-contract path, the launch_date payload and the secure verify_tls default, on top of the existing field-validation, output-shape and missing-icon tests.

CI

  • Fully green on b69f981: GitHub Actions (Test gophish + siblings, check-signed-commits, linked issue, PR title), CircleCI (ensure_formatting, linter, test-, build_docker_images- including gophish), and CLA (not required, org member).
  • All commits signed (%G? = G).
  • 0 unresolved review threads.

Follow-ups (non-blocking, out of scope here)

  • Stats are read at campaign-creation time only (open/click/submit are 0 at launch); wiring get_campaign_stats polling for progressive human-response reporting can be a later change.
  • Sibling injectors still use the bash-only [[ ... ]] Dockerfile guard; aligning them is a separate cleanup.

Approval status

  • Formal GitHub approval could not be recorded: this review runs under the PR author's account (SamuelHassine) and GitHub does not allow approving your own PR ("Can not approve your own pull request"). Treat this comment as an equivalent positive review (LGTM) - a different maintainer needs to click Approve. mergeStateStatus is BLOCKED solely on REVIEW_REQUIRED; everything else is green and mergeable is MERGEABLE.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 19 out of 22 changed files in this pull request and generated 3 comments.

Comment thread gophish/gophish_injector/openaev_gophish.py
Comment thread gophish/gophish_injector/client/gophish_client.py Outdated
Comment thread gophish/gophish_injector/client/gophish_client.py Outdated
Improve operational diagnosability of the Gophish injector:

- Log the injection failure at error level (with inject id) in the
  process_message exception path, per the common-implementation logging
  guidance, so container logs carry the failure context.
- Enrich client HTTP error messages with the trimmed Gophish response
  body (which carries the actionable reason: missing template / page /
  group, permission or 404 details) instead of only the status line,
  and log it at error level via the previously unused client logger.
- Cover the new behaviour: injector error logging, and campaign / stats
  HTTP errors including the response body.
@SamuelHassine

Copy link
Copy Markdown
Member Author

Review and fix summary (diagnosability pass)

Re-ran the full end-to-end review: re-read every changed file (not just the diff) against sibling injectors (nuclei, nmap, shodan, http-query, teams) and injector_common, addressed the remaining open review comments, and applied the fixes in 183249a.

Review threads addressed (all resolved)

  • Local error logging (should-fix): the process_message exception path now logs at error level with the inject id (self.helper.injector_logger.error("Gophish injection failed", {"inject_id": ..., "error": ...})), matching the logging guidance in docs/inject_types/01-common-implementation.md, so container logs carry the failure context instead of only reporting back to OpenAEV.
  • Campaign-creation error body (should-fix): client HTTP errors now surface the trimmed Gophish response body (missing template / page / group, permission or 404 details) in the returned message and error logs, via a shared _error_result helper, instead of only the status line.
  • Stats-fetch error body (should-fix): get_campaign_stats shares the same helper, so its HTTP errors also include the trimmed response body.

Independent finding fixed

  • The client accepted a logger argument but never used it (dead code). It is now wired to the HTTP error paths, so client-level failures are logged at error level.

Tests

  • 14 tests, all passing locally (isort repo-root profile / black / flake8 clean). Added coverage for the injector error-logging path and for campaign / stats HTTP errors including the response body, on top of the existing field-validation, unsupported-contract, output-shape, launch_date, verify_tls and missing-icon tests.

CI

  • Fully green on 183249a: GitHub Actions (Test gophish + siblings, check-signed-commits, linked issue, PR title), CircleCI (ensure_formatting, linter, test-*), codecov/patch and codecov/project, and CLA (not required, org member).
  • All commits signed (%G? = G).
  • 0 unresolved review threads.

Follow-ups (non-blocking, out of scope here)

  • Stats are read at campaign-creation time only (open/click/submit are 0 at launch); wiring get_campaign_stats polling for progressive human-response reporting can be a later change.
  • Sibling injectors still use the bash-only [[ ... ]] Dockerfile guard; aligning them is a separate cleanup.

Approval status

  • Formal GitHub approval could not be recorded: this review runs under the PR author's account (SamuelHassine) and GitHub does not allow approving your own PR ("Can not approve your own pull request"). Treat this comment as an equivalent positive review (LGTM) - a different maintainer needs to click Approve. mergeStateStatus is BLOCKED solely on REVIEW_REQUIRED; everything else is green and mergeable is MERGEABLE.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Build the Gophish phishing campaign injector

3 participants