feat(phishing): add fully native phishing injector (#319) - #329
feat(phishing): add fully native phishing injector (#319)#329SamuelHassine wants to merge 7 commits into
Conversation
Self-contained phishing injector with an embedded SMTP sender and stdlib tracking web server (open pixel, click landing page, credential submission), requiring no external phishing platform. Wires the injector into the CircleCI build, publish and test matrices.
There was a problem hiding this comment.
Pull request overview
This PR introduces a new phishing/ injector that runs fully natively (SMTP sending + embedded stdlib tracking server) and wires it into the existing CircleCI test/build/publish matrices so it’s built and tested alongside the other injectors.
Changes:
- Added a new native phishing injector package with configuration, contracts, SMTP sender, template rendering, and an embedded tracking HTTP server.
- Added an initial unittest suite covering contracts, template rendering, recipient parsing, and live tracking endpoints.
- Updated CircleCI matrices to include the new
phishinginjector in test/build/publish jobs.
Reviewed changes
Copilot reviewed 21 out of 25 changed files in this pull request and generated 10 comments.
Show a summary per file
| File | Description |
|---|---|
| phishing/test/test_phishing.py | Adds unit/integration tests for contracts/templates/parsing and the embedded tracking server. |
| phishing/test/init.py | Marks the phishing test directory as a package. |
| phishing/README.md | Documents the injector’s purpose, constraints, and development workflow. |
| phishing/pyproject.toml | Defines project metadata, dependencies, dev tooling, and icon path metadata. |
| phishing/phishing_injector/tracking/store.py | Implements an in-memory campaign store for token→recipient mapping and event stats. |
| phishing/phishing_injector/tracking/server.py | Implements the embedded tracking HTTP server (open pixel, click landing, submit redirect). |
| phishing/phishing_injector/tracking/init.py | Marks the tracking module as a package. |
| phishing/phishing_injector/openaev_phishing.py | Main injector entrypoint: config loading, SMTP sending loop, tracking server startup, callback reporting. |
| phishing/phishing_injector/img/README.md | Describes required icon asset and icon standard constraints. |
| phishing/phishing_injector/helpers/templates.py | Adds built-in phishing templates and rendering with per-recipient tracking URLs. |
| phishing/phishing_injector/helpers/phishing_sender.py | Adds SMTP sender helper (stdlib smtplib + email.message). |
| phishing/phishing_injector/helpers/init.py | Marks helpers as a package. |
| phishing/phishing_injector/contracts_phishing.py | Defines the phishing contract (fields + manual “Human Response” expectation). |
| phishing/phishing_injector/configuration/phishing_config.py | Adds pydantic settings model for tracking + SMTP configuration. |
| phishing/phishing_injector/configuration/injector_config_override.py | Defines injector-level overrides (id/name/icon path). |
| phishing/phishing_injector/configuration/config_loader.py | Wires configuration into the OpenAEV daemon config hints and injects contracts. |
| phishing/phishing_injector/configuration/init.py | Marks configuration as a package. |
| phishing/phishing_injector/init.py | Adds package marker and a __version__. |
| phishing/manifest-metadata.json | Adds integration catalog metadata for the phishing injector container. |
| phishing/Dockerfile | Adds multi-stage build/run image for the phishing injector. |
| phishing/docker-compose.yml | Adds a sample compose service for local runs. |
| phishing/config.yml.sample | Adds sample YAML configuration for OpenAEV + injector + phishing settings. |
| phishing/.env.sample | Adds sample environment variable configuration. |
| phishing/.dockerignore | Adds dockerignore rules for the phishing injector build context. |
| .circleci/config.yml | Adds phishing to the CircleCI test/build/publish matrices. |
Adds coverage for __init__, process_message campaign flow, start and the SMTP sender to satisfy the codecov patch gate.
Address review findings on the native phishing injector: - Add a placeholder 512x512 icon-phishing.png (solid opaque background) and resolve ICON_PATH relative to the module so the injector no longer crashes with FileNotFoundError at startup. Final mark to be set in #305. - Record open/click/submit under the store lock to avoid a data race with stats() on the threaded tracking server. - Validate and cap the submission Content-Length (return 413) so a malformed or oversized header cannot 500 or tie up a handler thread. - Send no-store cache headers on the open pixel to avoid undercounting opens. - Join the tracking server thread on stop() for deterministic shutdown. - Stop double-encoding execution_output_structured and drop the always-zero send-time stats; the launch callback now reports sent/total and the tracking URL, and the README explains human response is scored via the manual expectation. - Add a unit test for the SMTP error path in PhishingSender.send.
Review and fix summaryDid an independent senior-reviewer pass over the full changed files (not just the diff) and applied all fixes in 36b0215 (signed). All 10 automated review threads are resolved and CI is fully green. Fixes applied
Status
Suggested follow-ups (out of scope)
|
Address the second round of review feedback on the native phishing injector: - Surface per-recipient send failures on partial success. The launch callback now appends the failed recipients to the execution message when at least one email is sent (status stays SUCCESS so delivered recipients remain trackable), instead of dropping the error details whenever sent > 0. - Guard TrackingServer.stop() behind the started-thread check so calling it before start() no longer risks a deadlock from httpd.shutdown() being invoked while serve_forever() is not running; server_close() still runs unconditionally to release the socket. - Update the recipients field label to mention semicolons, matching what parse_recipients() (and its test) already accept. - Add unit tests for the partial-failure message and for stop()-before-start().
Second review pass - completeAll three newly landed Copilot threads are addressed in 488f0d4, and an independent full-file re-review found nothing else blocking. New comments addressed
Independent re-reviewNo further blocking findings. Re-confirmed the first-pass hardening is intact: store mutations under the lock, no-store headers on the open pixel, Content-Length cap (413) in do_POST, icon shipped and resolved relative to the module, and execution_output_structured as a single flat json.dumps. Verified the injector's SUCCESS/ERROR status strings match the platform trace-status contract. Tests / CI
BlockerApproval could not be recorded here because GitHub does not allow approving your own PR (this gh account is the PR author). A second maintainer must submit the approving review. Everything else is ready. |
#319) The submission endpoint capped Content-Length but never set a socket read timeout, so a client could advertise a small body and then slow-send or stall it, pinning a handler thread indefinitely (slowloris-style) and defeating the size cap. Set a per-request timeout on the tracking handler so a stalled read raises and frees the thread. Add a unit test asserting the timeout is applied.
Third review pass - completeIndependent full-file re-review (not just the diff). One open thread remained from the previous round - the slowloris gap on the submission endpoint - and it is now fixed. Addressed
Status
BlockerApproval could not be recorded here because GitHub does not allow approving your own PR (this gh account is the PR author), which is why the PR still shows Follow-ups (out of scope)
|
…meout (#319) The credential-submission endpoint rejected an invalid or oversized Content-Length with 413 but left the connection open without draining the body, so leftover bytes on a keep-alive socket could desynchronize the next request. It also read the body with a per-request socket timeout in effect, so a slow/stalled client would raise a socket timeout that bubbled up to BaseHTTPRequestHandler and logged a traceback. do_POST now forces close_connection on the 413 path and catches read timeouts (TimeoutError/OSError) to close the connection cleanly instead of raising. Added a raw-socket test asserting an oversized Content-Length is rejected with 413, the submit is not recorded, and the server closes the connection.
Fourth review pass - completeIndependent full-file re-review (not just the diff). The one open thread from the previous round - the 413 rejection path on the submission endpoint - is now fixed. Addressed
Status
BlockerApproval could not be recorded here because GitHub does not allow approving your own PR (this gh account is the PR author), which is why the PR still shows Follow-ups (out of scope)
|
Summary
phishingto the CircleCIbuild_docker_images,publish_imagesandtestmatrices.Notes
Review hardening (applied in this PR)
stats()on the threaded tracking server.Content-Length(return 413) so a malformed or oversized header cannot 500 or tie up a handler thread.Content-Lengthand then slow-sends or stalls the body cannot pin a handler thread indefinitely (slowloris), which would otherwise defeat the size cap.stop()for deterministic shutdown, and guardhttpd.shutdown()behind the started-thread check sostop()beforestart()cannot deadlock.FileNotFoundError.execution_output_structuredis now a singlejson.dumpsof a plain object (sent,total,tracking_url) - no nested JSON strings; the always-zero send-time stats were dropped since open/click/submit are scored via the contract's manual "Human Response" expectation.parse_recipients().stop()beforestart(), the request-timeout guard, and the oversized-Content-Length rejection (413 + connection close).Test plan
Follow-ups
Closes #319