Initial commit
This commit is contained in:
12
.env.example
Normal file
12
.env.example
Normal file
@ -0,0 +1,12 @@
|
||||
# Email DLP Environment Configuration
|
||||
|
||||
# The base URL for the OpenAI-compatible API (e.g., vLLM or OpenAI)
|
||||
# If not set, it will default to the CLI argument --endpoint (defaulting to http://localhost:8000/v1)
|
||||
OPENAI_BASE_URL=http://localhost:8000/v1
|
||||
|
||||
# The API key for the LLM service.
|
||||
# For local vLLM, this is typically "not-needed" or any string.
|
||||
OPENAI_API_KEY=not-needed
|
||||
|
||||
# The model name to use for analysis (this can also be passed via --model)
|
||||
MODEL_NAME=Qwen/Qwen3.5-35B-A3B
|
||||
221
.gitignore
vendored
Normal file
221
.gitignore
vendored
Normal file
@ -0,0 +1,221 @@
|
||||
outputs/
|
||||
preview
|
||||
results/
|
||||
|
||||
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[codz]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py.cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
cover/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
.pybuilder/
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
# For a library or package, you might want to ignore these files since the code is
|
||||
# intended to run in multiple environments; otherwise, check them in:
|
||||
# .python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
# Pipfile.lock
|
||||
|
||||
# UV
|
||||
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
# uv.lock
|
||||
|
||||
# poetry
|
||||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||
# poetry.lock
|
||||
# poetry.toml
|
||||
|
||||
# pdm
|
||||
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
|
||||
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
|
||||
# pdm.lock
|
||||
# pdm.toml
|
||||
.pdm-python
|
||||
.pdm-build/
|
||||
|
||||
# pixi
|
||||
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
|
||||
# pixi.lock
|
||||
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
|
||||
# in the .venv directory. It is recommended not to include this directory in version control.
|
||||
.pixi
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# Redis
|
||||
*.rdb
|
||||
*.aof
|
||||
*.pid
|
||||
|
||||
# RabbitMQ
|
||||
mnesia/
|
||||
rabbitmq/
|
||||
rabbitmq-data/
|
||||
|
||||
# ActiveMQ
|
||||
activemq-data/
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.envrc
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# pytype static type analyzer
|
||||
.pytype/
|
||||
|
||||
# Cython debug symbols
|
||||
cython_debug/
|
||||
|
||||
# PyCharm
|
||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
# .idea/
|
||||
|
||||
# Abstra
|
||||
# Abstra is an AI-powered process automation framework.
|
||||
# Ignore directories containing user credentials, local state, and settings.
|
||||
# Learn more at https://abstra.io/docs
|
||||
.abstra/
|
||||
|
||||
# Visual Studio Code
|
||||
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
|
||||
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. However, if you prefer,
|
||||
# you could uncomment the following to ignore the entire vscode folder
|
||||
# .vscode/
|
||||
|
||||
# Ruff stuff:
|
||||
.ruff_cache/
|
||||
|
||||
# PyPI configuration file
|
||||
.pypirc
|
||||
|
||||
# Marimo
|
||||
marimo/_static/
|
||||
marimo/_lsp/
|
||||
__marimo__/
|
||||
|
||||
# Streamlit
|
||||
.streamlit/secrets.toml
|
||||
1
.python-version
Normal file
1
.python-version
Normal file
@ -0,0 +1 @@
|
||||
3.11
|
||||
99
README.md
Normal file
99
README.md
Normal file
@ -0,0 +1,99 @@
|
||||
# Email DLP
|
||||
|
||||
An Email Data Loss Prevention (DLP) proof-of-concept tool that scans `.eml` files for policy violations using local or remote Large Language Models (LLMs). It extracts email content, parses and converts attachments, and evaluates the consolidated data against security policies to determine data risk levels and remediation actions (`BLOCK`, `ALERT`, or `PASS`).
|
||||
|
||||
## Features
|
||||
|
||||
- **Parse & Extract**: Parses raw MIME `.eml` files to extract metadata (Sender, Recipient, Subject, Date) and body text.
|
||||
- **Deep Attachment Conversion**: Automatically extracts and converts attachments into text representations. Now includes extraction of images from **PDF**, **Office** documents, and supports **.zip** / **.7z** archives.
|
||||
- **Heuristic Policy Review**: A fast, deterministic local engine that evaluates content against signals derived directly from `policy.py` (PII, Financial, Source Code, etc.) using keyword matching and context boosts.
|
||||
- **Preview Mode**: Inspect the parsed structure, metadata, and extracted text of emails and attachments without making any LLM calls.
|
||||
- **Simulation Mode**: Run a fast, deterministic local simulation of the DLP analysis without hitting an external LLM, which is extremely useful for local testing or CI environments.
|
||||
- **LLM-Powered Analysis**: Conduct full DLP analysis by passing the extracted content and a configured policy prompt to an OpenAI-compatible LLM endpoint (defaulting to local vLLM).
|
||||
|
||||
## Installation
|
||||
|
||||
This project requires **Python 3.11+** and relies on [`uv`](https://github.com/astral-sh/uv) for dependency management.
|
||||
|
||||
```bash
|
||||
# Install dependencies
|
||||
uv sync
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
You can use a `.env` file to manage your LLM credentials and endpoint. Copy the provided example to get started:
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
The supported environment variables are:
|
||||
|
||||
- `OPENAI_BASE_URL`: The API endpoint (e.g., `http://localhost:8000/v1`).
|
||||
- `OPENAI_API_KEY`: Your API key (use `not-needed` for local vLLM).
|
||||
- `MODEL_NAME`: The model name to use for analysis (e.g. `Qwen/Qwen3.5-35B-A3B`).
|
||||
|
||||
If these variables are set in `.env`, they will be used as defaults, although you can still override them using CLI flags like `--endpoint` and `--model`.
|
||||
|
||||
## Usage
|
||||
|
||||
The CLI is built with Typer and provides three main commands: `preview`, `simulate`, and `analyze`.
|
||||
|
||||
You can view the main help menu via:
|
||||
|
||||
```bash
|
||||
uv run email-dlp --help
|
||||
```
|
||||
|
||||
### 1. Preview
|
||||
|
||||
Preview the parsed email content, converted attachment text, and the system prompt that would be sent to the LLM.
|
||||
|
||||
```bash
|
||||
uv run email-dlp preview \
|
||||
--input data \
|
||||
--output preview-output \
|
||||
--include-system-prompt
|
||||
```
|
||||
|
||||
### 2. Simulate
|
||||
|
||||
Execute a local simulation of the DLP parsing and analysis pipeline. This skips the LLM call entirely and returns mock evaluation results, outputting to directories like `output/<timestamp>`.
|
||||
|
||||
```bash
|
||||
uv run email-dlp simulate \
|
||||
--input data \
|
||||
--output output/simulated \
|
||||
--summary
|
||||
```
|
||||
|
||||
### 3. Analyze
|
||||
|
||||
Perform real DLP evaluation using an LLM. By default, it targets a local OpenAI-compatible endpoint. It calculates risk scores, identifies specific violation types, and determines policy actions.
|
||||
|
||||
```bash
|
||||
uv run email-dlp analyze \
|
||||
--input data \
|
||||
--output output/llm-preds \
|
||||
--endpoint "http://localhost:8000/v1" \
|
||||
--model "Qwen/Qwen3.5-35B-A3B" \
|
||||
--summary
|
||||
```
|
||||
|
||||
## Outputs
|
||||
|
||||
Analysis and Simulation runs will create `.json` files in your specified output directory for every analyzed `.eml` file.
|
||||
|
||||
Additionally, a `batch_summary.json` will be generated, containing:
|
||||
|
||||
- Total number of emails processed
|
||||
- Breakdown of actions taken (`BLOCK`, `ALERT`, `PASS`)
|
||||
- Distribution of identified risk levels (`CRITICAL`, `HIGH`, `MEDIUM`, `LOW`)
|
||||
- Array of high-level email summaries with hit properties.
|
||||
|
||||
## Architecture Highlights
|
||||
|
||||
- Uses `markitdown`, `py7zr`, and `PyMuPDF` for deep file-parsing, archive unpacking, and image extraction.
|
||||
- Validation and schema enforcement is handled via `pydantic`.
|
||||
- Rich console outputs (spinners, progress bars, and formatted tables) using `rich`.
|
||||
BIN
assets/newsletter_march_2026.pdf
Normal file
BIN
assets/newsletter_march_2026.pdf
Normal file
Binary file not shown.
BIN
assets/test.jpg
Normal file
BIN
assets/test.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.1 KiB |
@ -0,0 +1,232 @@
|
||||
Received: from BWNEWEXCH02.boardware.com.mo (172.16.21.3) by
|
||||
BWNEWEXCH01.boardware.com.mo (172.16.21.2) with Microsoft SMTP Server
|
||||
(version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.2.1544.4
|
||||
via Mailbox Transport; Wed, 18 Mar 2026 17:17:43 +0800
|
||||
Received: from BWNEWEXCH01.boardware.com.mo (172.16.21.2) by
|
||||
BWNEWEXCH02.boardware.com.mo (172.16.21.3) with Microsoft SMTP Server
|
||||
(version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id
|
||||
15.2.1544.4; Wed, 18 Mar 2026 17:17:43 +0800
|
||||
Received: from BWNEWEXCH01.boardware.com.mo ([fe80::50d4:5d28:928f:9c72]) by
|
||||
BWNEWEXCH01.boardware.com.mo ([fe80::50d4:5d28:928f:9c72%16]) with mapi id
|
||||
15.02.1544.004; Wed, 18 Mar 2026 17:17:43 +0800
|
||||
From: "Lukey KW Leong (Boardware)" <lukey.kw.leong@boardware.com>
|
||||
To: "Ricky IW Chan (Boardware)" <ricky.iw.chan@boardware.com>
|
||||
Subject: CONFIDENTIAL: Atlas Decision Engine (ADE) Source Code & Weights for
|
||||
Review
|
||||
Thread-Topic: CONFIDENTIAL: Atlas Decision Engine (ADE) Source Code & Weights
|
||||
for Review
|
||||
Thread-Index: Ady2t0CF7XHYINjSTO6QdZ/RHtjunQ==
|
||||
Date: Wed, 18 Mar 2026 17:17:43 +0800
|
||||
Message-ID: <da48674352a34cd196ebe59c35caaed4@boardware.com>
|
||||
Accept-Language: en-US, zh-CN
|
||||
Content-Language: en-US
|
||||
X-MS-Has-Attach: yes
|
||||
X-MS-Exchange-Organization-SCL: -1
|
||||
X-MS-TNEF-Correlator: <da48674352a34cd196ebe59c35caaed4@boardware.com>
|
||||
MIME-Version: 1.0
|
||||
X-MS-Exchange-Organization-MessageDirectionality: Originating
|
||||
X-MS-Exchange-Organization-AuthSource: BWNEWEXCH01.boardware.com.mo
|
||||
X-MS-Exchange-Organization-AuthAs: Internal
|
||||
X-MS-Exchange-Organization-AuthMechanism: 04
|
||||
X-Originating-IP: [10.130.2.6]
|
||||
X-MS-Exchange-Organization-Network-Message-Id: 74ae67aa-6b49-4793-f714-08de84cf3634
|
||||
Return-Path: lukey.kw.leong@boardware.com
|
||||
X-MS-Exchange-Transport-EndToEndLatency: 00:00:00.3666690
|
||||
X-MS-Exchange-Processed-By-BccFoldering: 15.02.1544.004
|
||||
Content-type: multipart/mixed;
|
||||
boundary="B_3856699077_3802436304"
|
||||
|
||||
> This message is in MIME format. Since your mail reader does not understand
|
||||
this format, some or all of this message may not be legible.
|
||||
|
||||
--B_3856699077_3802436304
|
||||
Content-type: multipart/alternative;
|
||||
boundary="B_3856699077_193978940"
|
||||
|
||||
|
||||
--B_3856699077_193978940
|
||||
Content-type: text/plain;
|
||||
charset="UTF-8"
|
||||
Content-transfer-encoding: 7bit
|
||||
|
||||
Hi Team,
|
||||
|
||||
|
||||
|
||||
Please find below the code logic for the Atlas Decision Engine (ADE) as requested. This contains the core underwriting and risk-assessment system used to compute an application's outcome.
|
||||
|
||||
|
||||
|
||||
Important: This code is Copyright (c) 2018-2025 Hyperbolic Mortgage All Rights Reserved. Do not share outside the project group.
|
||||
|
||||
|
||||
|
||||
The attachment is the snippet regarding the scoring aggregator and proprietary weights, please review the attached file.
|
||||
|
||||
|
||||
|
||||
We also need to discuss how we handle applicant_id and credit_score data in the ADEInputs class when transferring to the external API.
|
||||
|
||||
Please confirm receipt.
|
||||
|
||||
|
||||
|
||||
Best regards,
|
||||
|
||||
Dev Team
|
||||
|
||||
|
||||
|
||||
|
||||
--B_3856699077_193978940
|
||||
Content-type: text/html;
|
||||
charset="UTF-8"
|
||||
Content-transfer-encoding: quoted-printable
|
||||
|
||||
<html xmlns:v=3D"urn:schemas-microsoft-com:vml" xmlns:o=3D"urn:schemas-microsof=
|
||||
t-com:office:office" xmlns:w=3D"urn:schemas-microsoft-com:office:word" xmlns:m=
|
||||
=3D"http://schemas.microsoft.com/office/2004/12/omml" xmlns=3D"http://www.w3.org=
|
||||
/TR/REC-html40">
|
||||
<head>
|
||||
<meta http-equiv=3D"Content-Type" content=3D"text/html; charset=3Dutf-8">
|
||||
<meta name=3D"Generator" content=3D"Microsoft Word 15 (filtered medium)">
|
||||
<style><!--
|
||||
/* Font Definitions */
|
||||
@font-face
|
||||
{font-family:PMingLiU;
|
||||
panose-1:2 2 5 0 0 0 0 0 0 0;}
|
||||
@font-face
|
||||
{font-family:"Cambria Math";
|
||||
panose-1:2 4 5 3 5 4 6 3 2 4;}
|
||||
@font-face
|
||||
{font-family:DengXian;
|
||||
panose-1:2 1 6 0 3 1 1 1 1 1;}
|
||||
@font-face
|
||||
{font-family:Aptos;}
|
||||
@font-face
|
||||
{font-family:"Segoe UI Emoji";
|
||||
panose-1:2 11 5 2 4 2 4 2 2 3;}
|
||||
@font-face
|
||||
{font-family:"\@DengXian";
|
||||
panose-1:2 1 6 0 3 1 1 1 1 1;}
|
||||
@font-face
|
||||
{font-family:"\@PMingLiU";
|
||||
panose-1:2 1 6 1 0 1 1 1 1 1;}
|
||||
/* Style Definitions */
|
||||
p.MsoNormal, li.MsoNormal, div.MsoNormal
|
||||
{margin:0in;
|
||||
font-size:12.0pt;
|
||||
font-family:"Aptos",sans-serif;
|
||||
mso-ligatures:standardcontextual;}
|
||||
span.EmailStyle17
|
||||
{mso-style-type:personal-compose;
|
||||
font-family:"Aptos",sans-serif;
|
||||
color:windowtext;}
|
||||
.MsoChpDefault
|
||||
{mso-style-type:export-only;}
|
||||
@page WordSection1
|
||||
{size:8.5in 11.0in;
|
||||
margin:1.0in 1.0in 1.0in 1.0in;}
|
||||
div.WordSection1
|
||||
{page:WordSection1;}
|
||||
--></style><!--[if gte mso 9]><xml>
|
||||
<o:shapedefaults v:ext=3D"edit" spidmax=3D"1026" />
|
||||
</xml><![endif]--><!--[if gte mso 9]><xml>
|
||||
<o:shapelayout v:ext=3D"edit">
|
||||
<o:idmap v:ext=3D"edit" data=3D"1" />
|
||||
</o:shapelayout></xml><![endif]-->
|
||||
</head>
|
||||
<body lang=3D"EN-US" link=3D"#467886" vlink=3D"#96607D" style=3D"word-wrap:break-wo=
|
||||
rd">
|
||||
<div class=3D"WordSection1">
|
||||
<p class=3D"MsoNormal">Hi Team,<span style=3D"mso-fareast-language:ZH-TW"><o:p>=
|
||||
</o:p></span></p>
|
||||
<p class=3D"MsoNormal"><span style=3D"mso-fareast-language:ZH-TW"><o:p> </=
|
||||
o:p></span></p>
|
||||
<p class=3D"MsoNormal">Please find below the <span style=3D"mso-fareast-languag=
|
||||
e:ZH-TW">
|
||||
code</span> logic for the <b>Atlas Decision Engine (ADE)</b> as requested. =
|
||||
This contains the core underwriting and risk-assessment system used to compu=
|
||||
te an application's outcome.<span style=3D"mso-fareast-language:ZH-TW"><o:p></=
|
||||
o:p></span></p>
|
||||
<p class=3D"MsoNormal"><b><span style=3D"font-family:"Segoe UI Emoji"=
|
||||
,sans-serif;mso-fareast-language:ZH-TW"><o:p> </o:p></span></b></p>
|
||||
<p class=3D"MsoNormal"><b>Important:</b> This code is <b>Copyright (c) 2018-2=
|
||||
025 Hyperbolic Mortgage All Rights Reserved.</b> Do not share outside the pr=
|
||||
oject group.<o:p></o:p></p>
|
||||
<p class=3D"MsoNormal"><span style=3D"mso-fareast-language:ZH-TW"><o:p> </=
|
||||
o:p></span></p>
|
||||
<p class=3D"MsoNormal"><span style=3D"mso-fareast-language:ZH-TW">The attachmen=
|
||||
t </span>
|
||||
is the snippet regarding the scoring aggregator and proprietary weights<spa=
|
||||
n style=3D"mso-fareast-language:ZH-TW">, please review the attached file.<o:p>=
|
||||
</o:p></span></p>
|
||||
<p class=3D"MsoNormal"><span style=3D"mso-fareast-language:ZH-TW"><o:p> </=
|
||||
o:p></span></p>
|
||||
<p class=3D"MsoNormal">We also need to discuss how we handle applicant_id and=
|
||||
credit_score data in the ADEInputs class when transferring to the external =
|
||||
API.<o:p></o:p></p>
|
||||
<p class=3D"MsoNormal">Please confirm receipt.<span style=3D"mso-fareast-langua=
|
||||
ge:ZH-TW"><o:p></o:p></span></p>
|
||||
<p class=3D"MsoNormal"><span style=3D"mso-fareast-language:ZH-TW"><o:p> </=
|
||||
o:p></span></p>
|
||||
<p class=3D"MsoNormal">Best regards, <span style=3D"mso-fareast-language:ZH-TW"=
|
||||
><o:p></o:p></span></p>
|
||||
<p class=3D"MsoNormal">Dev Team<o:p></o:p></p>
|
||||
<p class=3D"MsoNormal"><o:p> </o:p></p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
--B_3856699077_193978940--
|
||||
|
||||
|
||||
--B_3856699077_3802436304
|
||||
Content-type: application/octet-stream; name="Internal Source Code.7z";
|
||||
x-mac-creator="4F50494D"
|
||||
Content-disposition: attachment;
|
||||
filename="Internal Source Code.7z"
|
||||
Content-transfer-encoding: base64
|
||||
|
||||
|
||||
N3q8ryccAASj2z8qUAcAAAAAAAB6AAAAAAAAAAgRyoXgFgUHSF0AEWf+iOjZxRHg1/kHV5jK
|
||||
vo8Q+3+MCKFr1uKveLu7C7OlAiygp+Q6bKFk9WsmJ5DNne3sOTjCWHh0boHIEAWW4cC+BxoI
|
||||
iEA/XL554i66+QYyksqGSxrUbx3TH2dlAwDaZ2nexlSC9OHNPkKe76nwlDqrui1VyjBq0pnR
|
||||
wuBze46kbZc5htBhRFNLRQnGgVM9ihiOi4aWIcNofIF0Tix05kuZo2OhJzquhDtTDaa0FKbo
|
||||
Dni09OwHtwwLRnA4aoFhpuaTydPwzwPosi3os3mgdVd3c2ancMuA4wbHzs0cAToJrGxFEaCz
|
||||
O088euU9JOu8qvScWA670cvhy+NBdBCDZwGQktMUAH+cl+j6Me0/cjk8FfJHSDvJ1mYebujM
|
||||
X9hCjhOl+iBh1Wmrfm3qMy6dePdP0HAwnCA87lVodTK3OQy1voC5xrGdBC2uiswkn2Wv8LwU
|
||||
gYz/3XZ7QYRYhgwRfszWGRoREuAxwTSHJ7X6ir5ViZDXJMNbMcDJElk0F6L/79EN7fFfCzhr
|
||||
fzg2uN6VV5Q4ENQ++BhL/iWdg1RgmlX9TeDyxjAbvMPnYCk85ptOiJ5olco9vcivNl7zw7Sz
|
||||
MpkrnAjsVSRKhEImJ1EaKi4XC5lpVEdf0kilwow8A5N09kJU4xNtzcAfBC2GRpLn1AvcMTGp
|
||||
AFbgoydLAt2jR9rToSJM5Rh9MsSNf7ucpHgrA7l3JohIATdaqbqe1uRJGWCWyez8odr0HpB/
|
||||
7DHYOgIFyRADZU6MM1Rfs7rjwUrBTiERyvY0fIa25mHRieuiT2yHrTc6DK7kLjqhu7CM/Ns8
|
||||
ARZOsdWsb5qRAMM1V32I+8rW278MdOn5vJRlJzGXFuJ/rccTBEmWaIVXTkvtwFJTTaycUO/0
|
||||
ke/2WPYRn3XdairyD+8qDyv+ymxR8oEPfXFKnRsWkASSGI4q16twF5VkOhRjRurAMzjZgu7z
|
||||
bLiTkdRbtw60qCruv0mIuhtAkQe/ET+hmNNelOpVYqedBszd6pIjh2oueuo3neYEnBYArf9L
|
||||
6NozikCT4i08D7cYq272AmsA1wVLhvfdjYrJfi8dL8TrCsHnYeUe0UUjUwi5CpNFXU7UOppW
|
||||
8hHZonMhDD7Bjw/i4+w4gTlxLTmLwcBYVig9IITnOReUm4G16MOJGbYc1+UH2rGk5SjE4v+O
|
||||
FQtjUhVWYPqRs3Q7rmjr5piovIsKg2EOvEBssYnZFKKWnlX4IFW6o2bXK0yr+UipuwMybbeN
|
||||
SUlSE5D3AIUECMt9znGPQTpXeCccY6EWx4+30KQ8hy13diGg1dGfycs33ACTa5QuJ2bK1yEV
|
||||
uIifIZpiEppwbrOWX8jY+8iQuOfyNrqWZVbsOmsb73hOGbTL5X3TKamPXuf6T77/JTp6Cp5R
|
||||
ODXzIpIvJC8RZRPvMMMjZKm4tWeW/1M4zx466szZmxm8iQ1tv3x+gw2gie0tmRz9y88Y/vMw
|
||||
rmaYfIOOroGJAxrm3O/DNRxJ2odP0YPd19YkM8LGgW2QEJ/+7ygV6deJRuZ78onoS0RbhuEo
|
||||
YQrGFHziafJDa2CnlDXiPA/L/9rEPHE+CnWRNf6Lo1aBfUJm6DXRMDKJAii8mxG1KN3VWtCl
|
||||
h2rfYqoVmrVAlBotKSBmo2NmzuVFp4UOGa9/5dWD8nKF2rx0bl7cf87obNpNKK+cTGfU7qaF
|
||||
zA08fNINWeqHwbl/e1O4IZwD6vBUXYwh+HQWJT3GzWbmR8e+HvO8Eveb5k6Qty11+NNMUJFN
|
||||
5psFSXZ5vpuaZUSOm8/OUihUZj+WwHw+FEUqqqo3f1S6m9okPT+ivmtJfFS+zIBUKHrt1coO
|
||||
YLsg3/M9zAlO8n6MThqHVHyhIycEpKPjhytusV9W7upHp8BxPtTVj/mESPhJUUJND26wKJzD
|
||||
IALmQX0XaKwbyUUBMG3/BjubieNTDpTFof5wVro3EeCm5VtSyLpnAplz+bONn95cF3Q94ktc
|
||||
pqC1MRIp7mvLCJikmssum2kd0KiIaZDDZggF/R0GQuXZkKkH1FEfjwqz5iFbSWfea68qR2l9
|
||||
X3Jk1Unb65EWGDCUoBlh2uycdghsHBuDHWMnkkFdcH24SuR+CKhsSC1lXvxXT2jUAfwLNGH6
|
||||
Bf5ZKJ68hoH1mo9zpTLErgQC9UB738kLfYPMduoiJH7wrom/HiPAuuBweYppsn4sRTTAHZha
|
||||
ijA/yDU/iW/kqm8c26eQpnPjv8i63Lp/Ml11tjM0Ebindn/fPa6j4sQMwRmlc8kYLbsE9q20
|
||||
8doUgZUYWVJIo56F3YNAnqJ+6O1GT2Qkc+K7tGcNDhzNFspqErpoHB0AsH1AdnrLd2QfSrZz
|
||||
tLs/lqcceQofTG/3cLm9DDelKpYPWXwSf3ZhcvfMV7TskD7iMkKRC3OoZqASf37oHeZOtV3Z
|
||||
7u2k51N9M51s0JEYHtPAzdsPDU2AHIh3PBdPE1MnxpeUWQJWInru7nkZeGzfhD0fSAWzCdCp
|
||||
QVZmJjfF8CSwB2ewzQABBAYAAQmHUAAHCwEAASEhAQEMlgYACAoBt3c2QAAABQEZCgAAAAAA
|
||||
AAAAAAARMQBJAG4AdABlAHIAbgBhAGwAIABTAG8AdQByAGMAZQAgAEMAbwBkAGUALgBwAHkA
|
||||
AAAZAgAAFAoBAJwC0Hi1ttwBFQYBACAAAAAAAA==
|
||||
--B_3856699077_3802436304--
|
||||
|
||||
@ -0,0 +1,255 @@
|
||||
Received: from BWNEWEXCH01.boardware.com.mo (172.16.21.2) by
|
||||
BWNEWEXCH01.boardware.com.mo (172.16.21.2) with Microsoft SMTP Server
|
||||
(version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.2.1544.4
|
||||
via Mailbox Transport; Wed, 18 Mar 2026 17:08:06 +0800
|
||||
Received: from BWNEWEXCH01.boardware.com.mo (172.16.21.2) by
|
||||
BWNEWEXCH01.boardware.com.mo (172.16.21.2) with Microsoft SMTP Server
|
||||
(version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id
|
||||
15.2.1544.4; Wed, 18 Mar 2026 17:08:06 +0800
|
||||
Received: from BWNEWEXCH01.boardware.com.mo ([fe80::50d4:5d28:928f:9c72]) by
|
||||
BWNEWEXCH01.boardware.com.mo ([fe80::50d4:5d28:928f:9c72%16]) with mapi id
|
||||
15.02.1544.004; Wed, 18 Mar 2026 17:08:05 +0800
|
||||
From: "Lukey KW Leong (Boardware)" <lukey.kw.leong@boardware.com>
|
||||
To: "Ricky IW Chan (Boardware)" <ricky.iw.chan@boardware.com>
|
||||
Subject: (CONFIDENTIAL) Executed NDA: Hyperbolic Mortgage & Hyperbolic
|
||||
Construction
|
||||
Thread-Topic: (CONFIDENTIAL) Executed NDA: Hyperbolic Mortgage & Hyperbolic
|
||||
Construction
|
||||
Thread-Index: Ady2tq3H4KcRUr9KTCm6P5uRriepYg==
|
||||
Date: Wed, 18 Mar 2026 17:08:05 +0800
|
||||
Message-ID: <008300d108e94c99920f2665cba4e528@boardware.com>
|
||||
Accept-Language: en-US, zh-CN
|
||||
Content-Language: en-US
|
||||
X-MS-Has-Attach:
|
||||
X-MS-Exchange-Organization-SCL: -1
|
||||
X-MS-TNEF-Correlator: <008300d108e94c99920f2665cba4e528@boardware.com>
|
||||
MIME-Version: 1.0
|
||||
X-MS-Exchange-Organization-MessageDirectionality: Originating
|
||||
X-MS-Exchange-Organization-AuthSource: BWNEWEXCH01.boardware.com.mo
|
||||
X-MS-Exchange-Organization-AuthAs: Internal
|
||||
X-MS-Exchange-Organization-AuthMechanism: 04
|
||||
X-Originating-IP: [10.130.2.6]
|
||||
X-MS-Exchange-Organization-Network-Message-Id: 4b9cd223-559a-44e0-937e-08de84cdddee
|
||||
Return-Path: lukey.kw.leong@boardware.com
|
||||
X-MS-Exchange-Transport-EndToEndLatency: 00:00:00.4007588
|
||||
X-MS-Exchange-Processed-By-BccFoldering: 15.02.1544.004
|
||||
Content-type: multipart/alternative;
|
||||
boundary="B_3856698942_1521501271"
|
||||
|
||||
> This message is in MIME format. Since your mail reader does not understand
|
||||
this format, some or all of this message may not be legible.
|
||||
|
||||
--B_3856698942_1521501271
|
||||
Content-type: text/plain;
|
||||
charset="UTF-8"
|
||||
Content-transfer-encoding: 7bit
|
||||
|
||||
Hi,
|
||||
|
||||
|
||||
|
||||
For your records, I am forwarding the signed Nondisclosure Agreement (the "Agreement") between Hyperbolic Mortgage (Disclosing Party) and Hyperbolic Construction (Receiving Party), dated October 23, 2025 (p. 2).
|
||||
|
||||
This document defines our Confidential Information and outlines our obligations to maintain it in strictest confidence for the sole benefit of the Disclosing Party (p. 1).
|
||||
|
||||
Please note the following key terms:
|
||||
Definition: All materials stamped with "Confidential" or similar warnings are protected (p. 1).
|
||||
Restriction: Access is limited to employees and contractors who have signed similar nondisclosure restrictions (p. 1).
|
||||
Survival: These provisions remain in effect even after the termination of our working relationship (p. 1).
|
||||
This is a proprietary legal document signed by Jared Wilson (CEO, Hyperbolic Mortgage) and Jaya Sathe (CEO, Hyperbolic Construction) (p. 2). It should not be distributed to any unauthorized third parties.
|
||||
|
||||
|
||||
|
||||
Regards,
|
||||
|
||||
Lukey
|
||||
Legal Department
|
||||
Hyperbolic Mortgage
|
||||
|
||||
|
||||
|
||||
|
||||
--B_3856698942_1521501271
|
||||
Content-type: text/html;
|
||||
charset="UTF-8"
|
||||
Content-transfer-encoding: quoted-printable
|
||||
|
||||
<html xmlns:v=3D"urn:schemas-microsoft-com:vml" xmlns:o=3D"urn:schemas-microsof=
|
||||
t-com:office:office" xmlns:w=3D"urn:schemas-microsoft-com:office:word" xmlns:m=
|
||||
=3D"http://schemas.microsoft.com/office/2004/12/omml" xmlns=3D"http://www.w3.org=
|
||||
/TR/REC-html40">
|
||||
<head>
|
||||
<meta http-equiv=3D"Content-Type" content=3D"text/html; charset=3Dutf-8">
|
||||
<meta name=3D"Generator" content=3D"Microsoft Word 15 (filtered medium)">
|
||||
<style><!--
|
||||
/* Font Definitions */
|
||||
@font-face
|
||||
{font-family:Wingdings;
|
||||
panose-1:5 0 0 0 0 0 0 0 0 0;}
|
||||
@font-face
|
||||
{font-family:PMingLiU;
|
||||
panose-1:2 2 5 0 0 0 0 0 0 0;}
|
||||
@font-face
|
||||
{font-family:"Cambria Math";
|
||||
panose-1:2 4 5 3 5 4 6 3 2 4;}
|
||||
@font-face
|
||||
{font-family:DengXian;
|
||||
panose-1:2 1 6 0 3 1 1 1 1 1;}
|
||||
@font-face
|
||||
{font-family:Aptos;}
|
||||
@font-face
|
||||
{font-family:"\@DengXian";
|
||||
panose-1:2 1 6 0 3 1 1 1 1 1;}
|
||||
@font-face
|
||||
{font-family:"\@PMingLiU";
|
||||
panose-1:2 1 6 1 0 1 1 1 1 1;}
|
||||
/* Style Definitions */
|
||||
p.MsoNormal, li.MsoNormal, div.MsoNormal
|
||||
{margin:0in;
|
||||
font-size:12.0pt;
|
||||
font-family:"Aptos",sans-serif;
|
||||
mso-ligatures:standardcontextual;}
|
||||
span.EmailStyle17
|
||||
{mso-style-type:personal-compose;
|
||||
font-family:"Aptos",sans-serif;
|
||||
color:windowtext;}
|
||||
.MsoChpDefault
|
||||
{mso-style-type:export-only;}
|
||||
@page WordSection1
|
||||
{size:8.5in 11.0in;
|
||||
margin:1.0in 1.0in 1.0in 1.0in;}
|
||||
div.WordSection1
|
||||
{page:WordSection1;}
|
||||
/* List Definitions */
|
||||
@list l0
|
||||
{mso-list-id:1241797232;
|
||||
mso-list-template-ids:-1687115448;}
|
||||
@list l0:level1
|
||||
{mso-level-number-format:bullet;
|
||||
mso-level-text:\F0B7;
|
||||
mso-level-tab-stop:.5in;
|
||||
mso-level-number-position:left;
|
||||
text-indent:-.25in;
|
||||
mso-ansi-font-size:10.0pt;
|
||||
font-family:Symbol;}
|
||||
@list l0:level2
|
||||
{mso-level-number-format:bullet;
|
||||
mso-level-text:o;
|
||||
mso-level-tab-stop:1.0in;
|
||||
mso-level-number-position:left;
|
||||
text-indent:-.25in;
|
||||
mso-ansi-font-size:10.0pt;
|
||||
font-family:"Courier New";
|
||||
mso-bidi-font-family:"Times New Roman";}
|
||||
@list l0:level3
|
||||
{mso-level-number-format:bullet;
|
||||
mso-level-text:\F0A7;
|
||||
mso-level-tab-stop:1.5in;
|
||||
mso-level-number-position:left;
|
||||
text-indent:-.25in;
|
||||
mso-ansi-font-size:10.0pt;
|
||||
font-family:Wingdings;}
|
||||
@list l0:level4
|
||||
{mso-level-number-format:bullet;
|
||||
mso-level-text:\F0A7;
|
||||
mso-level-tab-stop:2.0in;
|
||||
mso-level-number-position:left;
|
||||
text-indent:-.25in;
|
||||
mso-ansi-font-size:10.0pt;
|
||||
font-family:Wingdings;}
|
||||
@list l0:level5
|
||||
{mso-level-number-format:bullet;
|
||||
mso-level-text:\F0A7;
|
||||
mso-level-tab-stop:2.5in;
|
||||
mso-level-number-position:left;
|
||||
text-indent:-.25in;
|
||||
mso-ansi-font-size:10.0pt;
|
||||
font-family:Wingdings;}
|
||||
@list l0:level6
|
||||
{mso-level-number-format:bullet;
|
||||
mso-level-text:\F0A7;
|
||||
mso-level-tab-stop:3.0in;
|
||||
mso-level-number-position:left;
|
||||
text-indent:-.25in;
|
||||
mso-ansi-font-size:10.0pt;
|
||||
font-family:Wingdings;}
|
||||
@list l0:level7
|
||||
{mso-level-number-format:bullet;
|
||||
mso-level-text:\F0A7;
|
||||
mso-level-tab-stop:3.5in;
|
||||
mso-level-number-position:left;
|
||||
text-indent:-.25in;
|
||||
mso-ansi-font-size:10.0pt;
|
||||
font-family:Wingdings;}
|
||||
@list l0:level8
|
||||
{mso-level-number-format:bullet;
|
||||
mso-level-text:\F0A7;
|
||||
mso-level-tab-stop:4.0in;
|
||||
mso-level-number-position:left;
|
||||
text-indent:-.25in;
|
||||
mso-ansi-font-size:10.0pt;
|
||||
font-family:Wingdings;}
|
||||
@list l0:level9
|
||||
{mso-level-number-format:bullet;
|
||||
mso-level-text:\F0A7;
|
||||
mso-level-tab-stop:4.5in;
|
||||
mso-level-number-position:left;
|
||||
text-indent:-.25in;
|
||||
mso-ansi-font-size:10.0pt;
|
||||
font-family:Wingdings;}
|
||||
ol
|
||||
{margin-bottom:0in;}
|
||||
ul
|
||||
{margin-bottom:0in;}
|
||||
--></style><!--[if gte mso 9]><xml>
|
||||
<o:shapedefaults v:ext=3D"edit" spidmax=3D"1026" />
|
||||
</xml><![endif]--><!--[if gte mso 9]><xml>
|
||||
<o:shapelayout v:ext=3D"edit">
|
||||
<o:idmap v:ext=3D"edit" data=3D"1" />
|
||||
</o:shapelayout></xml><![endif]-->
|
||||
</head>
|
||||
<body lang=3D"EN-US" link=3D"#467886" vlink=3D"#96607D" style=3D"word-wrap:break-wo=
|
||||
rd">
|
||||
<div class=3D"WordSection1">
|
||||
<p class=3D"MsoNormal">Hi,<span style=3D"mso-fareast-language:ZH-TW"><o:p></o:p=
|
||||
></span></p>
|
||||
<p class=3D"MsoNormal"><span style=3D"mso-fareast-language:ZH-TW"><o:p> </=
|
||||
o:p></span></p>
|
||||
<p class=3D"MsoNormal">For your records, I am forwarding the signed <b>N=
|
||||
ondisclosure Agreement (the "Agreement")</b> between <b>=
|
||||
Hyperbolic Mortgage</b> (Disclosing Party) and <b>Hyperbolic Const=
|
||||
ruction</b> (Receiving Party), dated October 23, 2025 (p. 2).<o:p></o:p=
|
||||
></p>
|
||||
<p class=3D"MsoNormal">This document defines our <b>Confidential Informa=
|
||||
tion</b> and outlines our obligations to maintain it in <b>stricte=
|
||||
st confidence</b> for the sole benefit of the Disclosing Party (p. 1).<=
|
||||
o:p></o:p></p>
|
||||
<p class=3D"MsoNormal">Please note the following key terms:<o:p></o:p></p>
|
||||
<ul style=3D"margin-top:0in" type=3D"disc">
|
||||
<li class=3D"MsoNormal" style=3D"mso-list:l0 level1 lfo1"><b>Definition:</b>&nb=
|
||||
sp;All materials stamped with <b>"Confidential"</b> or s=
|
||||
imilar warnings are protected (p. 1).<o:p></o:p></li><li class=3D"MsoNormal" s=
|
||||
tyle=3D"mso-list:l0 level1 lfo1"><b>Restriction:</b> Access is limited to=
|
||||
employees and contractors who have signed similar nondisclosure restriction=
|
||||
s (p. 1).<o:p></o:p></li><li class=3D"MsoNormal" style=3D"mso-list:l0 level1 lfo=
|
||||
1"><b>Survival:</b> These provisions remain in effect even after the te=
|
||||
rmination of our working relationship (p. 1).<o:p></o:p></li></ul>
|
||||
<p class=3D"MsoNormal">This is a <b>proprietary legal document</b> =
|
||||
signed by <b>Jared Wilson (CEO, Hyperbolic Mortgage)</b> and =
|
||||
<b>Jaya Sathe (CEO, Hyperbolic Construction)</b> (p. 2). It should not =
|
||||
be distributed to any unauthorized third parties.<o:p></o:p></p>
|
||||
<p class=3D"MsoNormal"><span style=3D"mso-fareast-language:ZH-TW"><o:p> </=
|
||||
o:p></span></p>
|
||||
<p class=3D"MsoNormal">Regards,<o:p></o:p></p>
|
||||
<p class=3D"MsoNormal"><span style=3D"mso-fareast-language:ZH-TW">Lukey</span><=
|
||||
br>
|
||||
Legal Department<br>
|
||||
<b>Hyperbolic Mortgage</b><o:p></o:p></p>
|
||||
<p class=3D"MsoNormal"><o:p> </o:p></p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
--B_3856698942_1521501271--
|
||||
|
||||
@ -0,0 +1,346 @@
|
||||
Received: from BWNEWEXCH02.boardware.com.mo (172.16.21.3) by
|
||||
BWNEWEXCH01.boardware.com.mo (172.16.21.2) with Microsoft SMTP Server
|
||||
(version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.2.1544.4
|
||||
via Mailbox Transport; Wed, 18 Mar 2026 17:01:16 +0800
|
||||
Received: from BWNEWEXCH01.boardware.com.mo (172.16.21.2) by
|
||||
BWNEWEXCH02.boardware.com.mo (172.16.21.3) with Microsoft SMTP Server
|
||||
(version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id
|
||||
15.2.1544.4; Wed, 18 Mar 2026 17:01:14 +0800
|
||||
Received: from BWNEWEXCH01.boardware.com.mo ([fe80::50d4:5d28:928f:9c72]) by
|
||||
BWNEWEXCH01.boardware.com.mo ([fe80::50d4:5d28:928f:9c72%16]) with mapi id
|
||||
15.02.1544.004; Wed, 18 Mar 2026 17:01:16 +0800
|
||||
From: "Lukey KW Leong (Boardware)" <lukey.kw.leong@boardware.com>
|
||||
To: "Ricky IW Chan (Boardware)" <ricky.iw.chan@boardware.com>
|
||||
Subject: (ACTION REQUIRED) External Review: Hyperbolic Mortgage Internal Data
|
||||
Security Policy v3.1
|
||||
Thread-Topic: (ACTION REQUIRED) External Review: Hyperbolic Mortgage Internal
|
||||
Data Security Policy v3.1
|
||||
Thread-Index: Ady2ta8QpT+uvnWKT1yzw+Y8XK8LOQ==
|
||||
Date: Wed, 18 Mar 2026 17:01:16 +0800
|
||||
Message-ID: <f8410ec9fcb84e22b1873e3f4b33dda6@boardware.com>
|
||||
Accept-Language: en-US, zh-CN
|
||||
Content-Language: en-US
|
||||
X-MS-Has-Attach: yes
|
||||
X-MS-Exchange-Organization-SCL: -1
|
||||
X-MS-TNEF-Correlator: <f8410ec9fcb84e22b1873e3f4b33dda6@boardware.com>
|
||||
MIME-Version: 1.0
|
||||
X-MS-Exchange-Organization-MessageDirectionality: Originating
|
||||
X-MS-Exchange-Organization-AuthSource: BWNEWEXCH01.boardware.com.mo
|
||||
X-MS-Exchange-Organization-AuthAs: Internal
|
||||
X-MS-Exchange-Organization-AuthMechanism: 04
|
||||
X-Originating-IP: [10.130.2.6]
|
||||
X-MS-Exchange-Organization-Network-Message-Id: 4253c263-330f-42e1-7b6b-08de84cce9e8
|
||||
Return-Path: lukey.kw.leong@boardware.com
|
||||
X-MS-Exchange-Transport-EndToEndLatency: 00:00:00.4116177
|
||||
X-MS-Exchange-Processed-By-BccFoldering: 15.02.1544.004
|
||||
Content-type: multipart/mixed;
|
||||
boundary="B_3856698953_771913467"
|
||||
|
||||
> This message is in MIME format. Since your mail reader does not understand
|
||||
this format, some or all of this message may not be legible.
|
||||
|
||||
--B_3856698953_771913467
|
||||
Content-type: multipart/alternative;
|
||||
boundary="B_3856698953_2299336799"
|
||||
|
||||
|
||||
--B_3856698953_2299336799
|
||||
Content-type: text/plain;
|
||||
charset="UTF-8"
|
||||
Content-transfer-encoding: quoted-printable
|
||||
|
||||
Hi,
|
||||
|
||||
=20
|
||||
|
||||
As discussed, I am forwarding the INTERNAL POLICY DOCUMENT for Hyperbolic M=
|
||||
ortgage regarding our Data Security & Acceptable Use Policy (Effective Date:=
|
||||
February 1, 2025).
|
||||
|
||||
This document is marked Internal Use Only =E2=80=94 Not for Public Distribution (=
|
||||
p. 2). However, I am sending this to your personal account so you can review=
|
||||
our internal PHI, PII, and financial records encryption standards and our c=
|
||||
loud and on-prem application security protocols (p. 1).
|
||||
|
||||
Key sections for your review:
|
||||
Customer financial data protection requirements.
|
||||
Third-party SaaS platform usage guidelines.
|
||||
Internal Incident Reporting procedures and security contact details (securi=
|
||||
ty@hyperbolicmortgage.com).
|
||||
Please ensure this remains confidential as it contains sensitive informatio=
|
||||
n about our borrower documents and access-controlled systems (p. 1).
|
||||
|
||||
=20
|
||||
|
||||
Best,
|
||||
|
||||
Lukey
|
||||
Hyperbolic Mortgage
|
||||
|
||||
=20
|
||||
|
||||
|
||||
--B_3856698953_2299336799
|
||||
Content-type: text/html;
|
||||
charset="UTF-8"
|
||||
Content-transfer-encoding: quoted-printable
|
||||
|
||||
<html xmlns:v=3D"urn:schemas-microsoft-com:vml" xmlns:o=3D"urn:schemas-microsof=
|
||||
t-com:office:office" xmlns:w=3D"urn:schemas-microsoft-com:office:word" xmlns:m=
|
||||
=3D"http://schemas.microsoft.com/office/2004/12/omml" xmlns=3D"http://www.w3.org=
|
||||
/TR/REC-html40">
|
||||
<head>
|
||||
<meta http-equiv=3D"Content-Type" content=3D"text/html; charset=3Dutf-8">
|
||||
<meta name=3D"Generator" content=3D"Microsoft Word 15 (filtered medium)">
|
||||
<style><!--
|
||||
/* Font Definitions */
|
||||
@font-face
|
||||
{font-family:Wingdings;
|
||||
panose-1:5 0 0 0 0 0 0 0 0 0;}
|
||||
@font-face
|
||||
{font-family:PMingLiU;
|
||||
panose-1:2 2 5 0 0 0 0 0 0 0;}
|
||||
@font-face
|
||||
{font-family:"Cambria Math";
|
||||
panose-1:2 4 5 3 5 4 6 3 2 4;}
|
||||
@font-face
|
||||
{font-family:DengXian;
|
||||
panose-1:2 1 6 0 3 1 1 1 1 1;}
|
||||
@font-face
|
||||
{font-family:Aptos;}
|
||||
@font-face
|
||||
{font-family:"\@DengXian";
|
||||
panose-1:2 1 6 0 3 1 1 1 1 1;}
|
||||
@font-face
|
||||
{font-family:"\@PMingLiU";
|
||||
panose-1:2 1 6 1 0 1 1 1 1 1;}
|
||||
/* Style Definitions */
|
||||
p.MsoNormal, li.MsoNormal, div.MsoNormal
|
||||
{margin:0in;
|
||||
font-size:12.0pt;
|
||||
font-family:"Aptos",sans-serif;
|
||||
mso-ligatures:standardcontextual;}
|
||||
span.EmailStyle17
|
||||
{mso-style-type:personal-compose;
|
||||
font-family:"Aptos",sans-serif;
|
||||
color:windowtext;}
|
||||
.MsoChpDefault
|
||||
{mso-style-type:export-only;}
|
||||
@page WordSection1
|
||||
{size:8.5in 11.0in;
|
||||
margin:1.0in 1.0in 1.0in 1.0in;}
|
||||
div.WordSection1
|
||||
{page:WordSection1;}
|
||||
/* List Definitions */
|
||||
@list l0
|
||||
{mso-list-id:1298031305;
|
||||
mso-list-template-ids:-438370642;}
|
||||
@list l0:level1
|
||||
{mso-level-number-format:bullet;
|
||||
mso-level-text:\F0B7;
|
||||
mso-level-tab-stop:.5in;
|
||||
mso-level-number-position:left;
|
||||
text-indent:-.25in;
|
||||
mso-ansi-font-size:10.0pt;
|
||||
font-family:Symbol;}
|
||||
@list l0:level2
|
||||
{mso-level-number-format:bullet;
|
||||
mso-level-text:o;
|
||||
mso-level-tab-stop:1.0in;
|
||||
mso-level-number-position:left;
|
||||
text-indent:-.25in;
|
||||
mso-ansi-font-size:10.0pt;
|
||||
font-family:"Courier New";
|
||||
mso-bidi-font-family:"Times New Roman";}
|
||||
@list l0:level3
|
||||
{mso-level-number-format:bullet;
|
||||
mso-level-text:\F0A7;
|
||||
mso-level-tab-stop:1.5in;
|
||||
mso-level-number-position:left;
|
||||
text-indent:-.25in;
|
||||
mso-ansi-font-size:10.0pt;
|
||||
font-family:Wingdings;}
|
||||
@list l0:level4
|
||||
{mso-level-number-format:bullet;
|
||||
mso-level-text:\F0A7;
|
||||
mso-level-tab-stop:2.0in;
|
||||
mso-level-number-position:left;
|
||||
text-indent:-.25in;
|
||||
mso-ansi-font-size:10.0pt;
|
||||
font-family:Wingdings;}
|
||||
@list l0:level5
|
||||
{mso-level-number-format:bullet;
|
||||
mso-level-text:\F0A7;
|
||||
mso-level-tab-stop:2.5in;
|
||||
mso-level-number-position:left;
|
||||
text-indent:-.25in;
|
||||
mso-ansi-font-size:10.0pt;
|
||||
font-family:Wingdings;}
|
||||
@list l0:level6
|
||||
{mso-level-number-format:bullet;
|
||||
mso-level-text:\F0A7;
|
||||
mso-level-tab-stop:3.0in;
|
||||
mso-level-number-position:left;
|
||||
text-indent:-.25in;
|
||||
mso-ansi-font-size:10.0pt;
|
||||
font-family:Wingdings;}
|
||||
@list l0:level7
|
||||
{mso-level-number-format:bullet;
|
||||
mso-level-text:\F0A7;
|
||||
mso-level-tab-stop:3.5in;
|
||||
mso-level-number-position:left;
|
||||
text-indent:-.25in;
|
||||
mso-ansi-font-size:10.0pt;
|
||||
font-family:Wingdings;}
|
||||
@list l0:level8
|
||||
{mso-level-number-format:bullet;
|
||||
mso-level-text:\F0A7;
|
||||
mso-level-tab-stop:4.0in;
|
||||
mso-level-number-position:left;
|
||||
text-indent:-.25in;
|
||||
mso-ansi-font-size:10.0pt;
|
||||
font-family:Wingdings;}
|
||||
@list l0:level9
|
||||
{mso-level-number-format:bullet;
|
||||
mso-level-text:\F0A7;
|
||||
mso-level-tab-stop:4.5in;
|
||||
mso-level-number-position:left;
|
||||
text-indent:-.25in;
|
||||
mso-ansi-font-size:10.0pt;
|
||||
font-family:Wingdings;}
|
||||
ol
|
||||
{margin-bottom:0in;}
|
||||
ul
|
||||
{margin-bottom:0in;}
|
||||
--></style><!--[if gte mso 9]><xml>
|
||||
<o:shapedefaults v:ext=3D"edit" spidmax=3D"1026" />
|
||||
</xml><![endif]--><!--[if gte mso 9]><xml>
|
||||
<o:shapelayout v:ext=3D"edit">
|
||||
<o:idmap v:ext=3D"edit" data=3D"1" />
|
||||
</o:shapelayout></xml><![endif]-->
|
||||
</head>
|
||||
<body lang=3D"EN-US" link=3D"#467886" vlink=3D"#96607D" style=3D"word-wrap:break-wo=
|
||||
rd">
|
||||
<div class=3D"WordSection1">
|
||||
<p class=3D"MsoNormal">Hi,<span style=3D"mso-fareast-language:ZH-TW"><o:p></o:p=
|
||||
></span></p>
|
||||
<p class=3D"MsoNormal"><span style=3D"mso-fareast-language:ZH-TW"><o:p> </=
|
||||
o:p></span></p>
|
||||
<p class=3D"MsoNormal">As discussed, I am forwarding the <b>INTERNAL POL=
|
||||
ICY DOCUMENT</b> for <b>Hyperbolic Mortgage</b> regarding our=
|
||||
<b>Data Security & Acceptable Use Policy</b> (Effective Date:=
|
||||
February 1, 2025).<o:p></o:p></p>
|
||||
<p class=3D"MsoNormal">This document is marked <b>Internal Use Only =E2=80=94 =
|
||||
Not for Public Distribution</b> (p. 2). However, I am sending this to y=
|
||||
our personal account so you can review our internal <b>PHI, PII, and fi=
|
||||
nancial records</b> encryption standards and our <b>cloud
|
||||
and on-prem application</b> security protocols (p. 1).<o:p></o:p></p>
|
||||
<p class=3D"MsoNormal">Key sections for your review:<o:p></o:p></p>
|
||||
<ul style=3D"margin-top:0in" type=3D"disc">
|
||||
<li class=3D"MsoNormal" style=3D"mso-list:l0 level1 lfo1"><b>Customer financial=
|
||||
data</b> protection requirements.<o:p></o:p></li><li class=3D"MsoNormal"=
|
||||
style=3D"mso-list:l0 level1 lfo1"><b>Third-party SaaS platform</b> usage=
|
||||
guidelines.<o:p></o:p></li><li class=3D"MsoNormal" style=3D"mso-list:l0 level1 =
|
||||
lfo1">Internal <b>Incident Reporting</b> procedures and security c=
|
||||
ontact details (security@hyperbolicmortgage.com).<o:p></o:p></li></ul>
|
||||
<p class=3D"MsoNormal">Please ensure this remains confidential as it contains=
|
||||
sensitive information about our <b>borrower documents</b> and&nbs=
|
||||
p;<b>access-controlled systems</b> (p. 1).<span style=3D"mso-fareast-lang=
|
||||
uage:ZH-TW"><o:p></o:p></span></p>
|
||||
<p class=3D"MsoNormal"><span style=3D"mso-fareast-language:ZH-TW"><o:p> </=
|
||||
o:p></span></p>
|
||||
<p class=3D"MsoNormal">Best,<o:p></o:p></p>
|
||||
<p class=3D"MsoNormal"><span style=3D"mso-fareast-language:ZH-TW">Lukey</span><=
|
||||
br>
|
||||
<b>Hyperbolic Mortgage</b><o:p></o:p></p>
|
||||
<p class=3D"MsoNormal"><o:p> </o:p></p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
--B_3856698953_2299336799--
|
||||
|
||||
|
||||
--B_3856698953_771913467
|
||||
Content-type: application/pdf; name="Policy File.pdf";
|
||||
x-mac-creator="4F50494D";
|
||||
x-mac-type="50444620"
|
||||
Content-disposition: attachment;
|
||||
filename="Policy File.pdf"
|
||||
Content-transfer-encoding: base64
|
||||
|
||||
|
||||
JVBERi0xLjQKJZOMi54gUmVwb3J0TGFiIEdlbmVyYXRlZCBQREYgZG9jdW1lbnQgaHR0cDov
|
||||
L3d3dy5yZXBvcnRsYWIuY29tCjEgMCBvYmoKPDwKL0YxIDIgMCBSIC9GMiAzIDAgUiAvRjMg
|
||||
NCAwIFIKPj4KZW5kb2JqCjIgMCBvYmoKPDwKL0Jhc2VGb250IC9IZWx2ZXRpY2EgL0VuY29k
|
||||
aW5nIC9XaW5BbnNpRW5jb2RpbmcgL05hbWUgL0YxIC9TdWJ0eXBlIC9UeXBlMSAvVHlwZSAv
|
||||
Rm9udAo+PgplbmRvYmoKMyAwIG9iago8PAovQmFzZUZvbnQgL0hlbHZldGljYS1Cb2xkIC9F
|
||||
bmNvZGluZyAvV2luQW5zaUVuY29kaW5nIC9OYW1lIC9GMiAvU3VidHlwZSAvVHlwZTEgL1R5
|
||||
cGUgL0ZvbnQKPj4KZW5kb2JqCjQgMCBvYmoKPDwKL0Jhc2VGb250IC9aYXBmRGluZ2JhdHMg
|
||||
L05hbWUgL0YzIC9TdWJ0eXBlIC9UeXBlMSAvVHlwZSAvRm9udAo+PgplbmRvYmoKNSAwIG9i
|
||||
ago8PAovQ29udGVudHMgMTAgMCBSIC9NZWRpYUJveCBbIDAgMCA2MTIgNzkyIF0gL1BhcmVu
|
||||
dCA5IDAgUiAvUmVzb3VyY2VzIDw8Ci9Gb250IDEgMCBSIC9Qcm9jU2V0IFsgL1BERiAvVGV4
|
||||
dCAvSW1hZ2VCIC9JbWFnZUMgL0ltYWdlSSBdCj4+IC9Sb3RhdGUgMCAvVHJhbnMgPDwKCj4+
|
||||
IAogIC9UeXBlIC9QYWdlCj4+CmVuZG9iago2IDAgb2JqCjw8Ci9Db250ZW50cyAxMSAwIFIg
|
||||
L01lZGlhQm94IFsgMCAwIDYxMiA3OTIgXSAvUGFyZW50IDkgMCBSIC9SZXNvdXJjZXMgPDwK
|
||||
L0ZvbnQgMSAwIFIgL1Byb2NTZXQgWyAvUERGIC9UZXh0IC9JbWFnZUIgL0ltYWdlQyAvSW1h
|
||||
Z2VJIF0KPj4gL1JvdGF0ZSAwIC9UcmFucyA8PAoKPj4gCiAgL1R5cGUgL1BhZ2UKPj4KZW5k
|
||||
b2JqCjcgMCBvYmoKPDwKL1BhZ2VNb2RlIC9Vc2VOb25lIC9QYWdlcyA5IDAgUiAvVHlwZSAv
|
||||
Q2F0YWxvZwo+PgplbmRvYmoKOCAwIG9iago8PAovQXV0aG9yIChcKGFub255bW91c1wpKSAv
|
||||
Q3JlYXRpb25EYXRlIChEOjIwMjUxMTIxMDIzODUwKzAwJzAwJykgL0NyZWF0b3IgKFwodW5z
|
||||
cGVjaWZpZWRcKSkgL0tleXdvcmRzICgpIC9Nb2REYXRlIChEOjIwMjUxMTIxMDIzODUwKzAw
|
||||
JzAwJykgL1Byb2R1Y2VyIChSZXBvcnRMYWIgUERGIExpYnJhcnkgLSB3d3cucmVwb3J0bGFi
|
||||
LmNvbSkgCiAgL1N1YmplY3QgKFwodW5zcGVjaWZpZWRcKSkgL1RpdGxlIChcKGFub255bW91
|
||||
c1wpKSAvVHJhcHBlZCAvRmFsc2UKPj4KZW5kb2JqCjkgMCBvYmoKPDwKL0NvdW50IDIgL0tp
|
||||
ZHMgWyA1IDAgUiA2IDAgUiBdIC9UeXBlIC9QYWdlcwo+PgplbmRvYmoKMTAgMCBvYmoKPDwK
|
||||
L0ZpbHRlciBbIC9BU0NJSTg1RGVjb2RlIC9GbGF0ZURlY29kZSBdIC9MZW5ndGggMTYwNwo+
|
||||
PgpzdHJlYW0KR2F0JSJnTVolMCY6TWwrYlohSVA+ZCo2b10yLVxqNW4lUV4+cnU3KWE5c1RJ
|
||||
Mkh1J2dQI1NhQFlPPEBvQFJrRmIoLk82UmpwXVMrRihXTEhgcVVoTDQzYFU3KEEuLFhUayRL
|
||||
SidkWkFGLD9mSmxsL0xYVTYuMmhzNGVuM0otV2w9T2o3cHAlJWhQJUtSMVBRLGJDbCQ3Yzgo
|
||||
ZixOYUtmJG5iRGFEKCwlTTNoKD5MMm1gbiZHX3U4Y09qZSRYO0osKz1OXWomaVg8Q2pVKz4s
|
||||
Pz1WPyEqI21yJzYxVEJebW4sK3FkLmlhNiw9SVA3bi9MSFtCcDgsQ0JbXShlWGxdTTE/ZWMw
|
||||
YHU2bVRZVV9LPy4jNGFqZW9WLmo8PzJwZCQ0bGdqdFxOWiQ1Tk0ta0BsRSgrSTYzWlVSK3Mj
|
||||
YFtKaj05Z0FEJlNRRkNUYyQyMz9wOHNzaTxcYi1MPW43bCpKNl45by50dCNQKTpFJVgzU3Ar
|
||||
X11JOTp1UTVOdC1EXEhBblBbKVZbWEs5VDYhNDZUR3BUJ1FPVjUncylqX1RER29PMitwLyI7
|
||||
ajgxaz9GKGxZWj9gJC45XWJxaSgwUSxVN1VgPyIoRzVBYC5dYGBDPiwmQyxyKSZRQmc+dTpM
|
||||
UElTTyFZTW1vN1IxZDhSR1RbKCRlW18jUS4maTRZVlNnYmNXQSVHcCxqTV5ARlNQSVJrP0Aq
|
||||
NEVqKkR1YHBXTjdFKVdFQltVPDw7STNPIWY8WXVNWUZNKFptTTFXZy9fKXE4U1V0PStxPCxe
|
||||
K20uRSpMVFE3PCo6VTciIjw/RUxcJ1o3QFBNLVU3R1Y9W2tdZ3BeYzcuXG5EcyU2PyJoND1T
|
||||
cVYmO0JBVmwwMEJGYW8kJ2g/XEE2LiJYJj86TXVMK2ArQUtXWk1SdFBBT0UhM0whbTRxOm1t
|
||||
WT5WKUAxY2QhZXNZSz9gQUtQcXA0UCQ7UCFHUnM7ODspOCprKkdBP1RFZCdoLy01a0VJRmxi
|
||||
ZlVUXHA0T1hhJFRDL0UmK1VTWCEuMkEvUWVlSlFQIk9FMWAnVXJLYmY0T0hoTFNYYypvNzsx
|
||||
PTJEXU1zS2NfUE9FSj9ULDpxcltvKjVca2pcSTtTW04uKiowZy4jalU9OmJLLlVAdD5iWio4
|
||||
JVxWYTpVPWJwSjQ6Y1M8XVUhL0RTWzB1cjBQXHRjZWdUM0VFJUAuWnBkOENMQ1klXExtcEBX
|
||||
VC1PMzlYbGIwdHEtYWwhXCNbTGxeW2NEOm8zQkJhSzFLbFxaK0NlSkI+TiF1Z2hULjQmZmY4
|
||||
PlVUNVRDSCVpLToyYG1sKzNhay9OdDo4Pi9FRzh1Tzw4Kk4yUXJYUzNgbGxeZ1NAS09OMlNN
|
||||
ZT00IiRiZDwsS2Q2cTpBbUVtazsuXUo3Q2hJa0pEL2xSUmtaR3RLJWUnYGs4LnVWMTdZZjg+
|
||||
Ymk9aVtFZyFwXyZMdS5HYDBQLF8qZCE7NS04I3NVT0xdRXIxXF9FYDExVT5eQCciNEtvXF5y
|
||||
K2E9KXRbPVpwIkZxWDhNRGFcXU1HS2RHNSdENUp1LiM3Iig5QztOWEp1VW1nbz1xX0lTQVxJ
|
||||
b1hcXGQvXlI1UHFANWIxTG9OI0tNZSosNnJOQVJXOE1MW2Y1bjJWJz5IQFdvK0I3OVluR0c8
|
||||
XWxELCVIO1InbkZwP1U3UTY5ZG1kSkE1WV9iMGJeJEBsUG9EQVVSZDEpOVFAQktmbkk3bjsw
|
||||
UVBwPmg7Lk9xRTtmWEVKVTtoREpQMyMhaUhOS21MdE0kbm5oRlhLX1ZJZWo6aS05YlNaNUdX
|
||||
MzdyOU9GNjojUWtgJSozLjBaKHAkZkJEQ0YrYVBxLzBzOjxabzUpQlsrWSdrJD5wRjo8N0xF
|
||||
LjlxM2ImY0JBVlFaVmhhVCUsYCMkaS44K0dJSFlZZDVqVXRdUixzOU4rZ0EzOkUkLyI1IWx0
|
||||
NVguJ1E8S2E5ZCdDRytUbVUiIlhxT1hablBXK3Upby1MKlpfPWlDMihvTS8lI0FCaEI4SXRu
|
||||
SlE1RUl1N1whTV5LO24vRjQ+LUBCKTtnViVHOUNiZjE6TF5HNj5CRyhyIVo4PkpUKFFvTEFd
|
||||
ZyYnSDs8QzEiaT1nKSZhcUpwM2VCKVhRJGdFLXA5P2tkZmpTVzVnTjtvcSlodVkwfj5lbmRz
|
||||
dHJlYW0KZW5kb2JqCjExIDAgb2JqCjw8Ci9GaWx0ZXIgWyAvQVNDSUk4NURlY29kZSAvRmxh
|
||||
dGVEZWNvZGUgXSAvTGVuZ3RoIDMxMgo+PgpzdHJlYW0KR2FwcFk5MkVHWiY7OU5KJ2x0cSk5
|
||||
Jmg9cjRuR1YqJ18pYXNgbDFOWFdLQ1xnTlhhLkdCNEQ4ZUgiRitsU1FUMGlAIzdrOyFQIkVJ
|
||||
KiFBWTlIJWAtZSRGN1soYmcpV2RPTG1ZbltNP144QHA4ZExDXlw8LToiSScjYFNzYD9xJllY
|
||||
TD1OUWJ1MEtQdWApMTsmSlY8T0kxLXU1UzVBayJSJFMtWCZfN14nX0I3U1VRT0NCY2lUXFJA
|
||||
PHVmcDMxPyU5JSNdWXU/Mm8pRHVNV3AnVUBINz0iMG47OE4mZ0VHZCYqSnAoMDROXWZvIkJj
|
||||
bkBsNDpOaFRiTG1sWGJNSzlpKW9zXSlxYT5cO11cPnFabWRUOT1ObT9aMjJaKnA7bFJAKTdg
|
||||
NU9iOmJOQWhOQUtncE9oVGZdTn4+ZW5kc3RyZWFtCmVuZG9iagp4cmVmCjAgMTIKMDAwMDAw
|
||||
MDAwMCA2NTUzNSBmIAowMDAwMDAwMDczIDAwMDAwIG4gCjAwMDAwMDAxMjQgMDAwMDAgbiAK
|
||||
MDAwMDAwMDIzMSAwMDAwMCBuIAowMDAwMDAwMzQzIDAwMDAwIG4gCjAwMDAwMDA0MjYgMDAw
|
||||
MDAgbiAKMDAwMDAwMDYyMCAwMDAwMCBuIAowMDAwMDAwODE0IDAwMDAwIG4gCjAwMDAwMDA4
|
||||
ODIgMDAwMDAgbiAKMDAwMDAwMTE2NSAwMDAwMCBuIAowMDAwMDAxMjMwIDAwMDAwIG4gCjAw
|
||||
MDAwMDI5MjkgMDAwMDAgbiAKdHJhaWxlcgo8PAovSUQgCls8NzA1NzcyZTcwMzE5OWRjMWVl
|
||||
YzkxODIyNDUwMTIyOGE+PDcwNTc3MmU3MDMxOTlkYzFlZWM5MTgyMjQ1MDEyMjhhPl0KJSBS
|
||||
ZXBvcnRMYWIgZ2VuZXJhdGVkIFBERiBkb2N1bWVudCAtLSBkaWdlc3QgKGh0dHA6Ly93d3cu
|
||||
cmVwb3J0bGFiLmNvbSkKCi9JbmZvIDggMCBSCi9Sb290IDcgMCBSCi9TaXplIDEyCj4+CnN0
|
||||
YXJ0eHJlZgozMzMyCiUlRU9GCg==
|
||||
--B_3856698953_771913467--
|
||||
|
||||
230
data/Full Customer List & Sales Data for Q1 Campaign.eml
Normal file
230
data/Full Customer List & Sales Data for Q1 Campaign.eml
Normal file
@ -0,0 +1,230 @@
|
||||
Received: from BWNEWEXCH01.boardware.com.mo (172.16.21.2) by
|
||||
BWNEWEXCH01.boardware.com.mo (172.16.21.2) with Microsoft SMTP Server
|
||||
(version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.2.1544.4
|
||||
via Mailbox Transport; Wed, 18 Mar 2026 17:23:21 +0800
|
||||
Received: from BWNEWEXCH01.boardware.com.mo (172.16.21.2) by
|
||||
BWNEWEXCH01.boardware.com.mo (172.16.21.2) with Microsoft SMTP Server
|
||||
(version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id
|
||||
15.2.1544.4; Wed, 18 Mar 2026 17:23:20 +0800
|
||||
Received: from BWNEWEXCH01.boardware.com.mo ([fe80::50d4:5d28:928f:9c72]) by
|
||||
BWNEWEXCH01.boardware.com.mo ([fe80::50d4:5d28:928f:9c72%16]) with mapi id
|
||||
15.02.1544.004; Wed, 18 Mar 2026 17:23:20 +0800
|
||||
From: "Lukey KW Leong (Boardware)" <lukey.kw.leong@boardware.com>
|
||||
To: "Ricky IW Chan (Boardware)" <ricky.iw.chan@boardware.com>
|
||||
Subject: URGENT: Full Customer List & Sales Data for Q1 Campaign
|
||||
Thread-Topic: URGENT: Full Customer List & Sales Data for Q1 Campaign
|
||||
Thread-Index: Ady2uGIzHiAOL6KDSNKITnJUWXGuTg==
|
||||
Date: Wed, 18 Mar 2026 17:23:20 +0800
|
||||
Message-ID: <9faf5abfe0d04fd58471a1a2b59d0cdb@boardware.com>
|
||||
Accept-Language: en-US, zh-CN
|
||||
Content-Language: en-US
|
||||
X-MS-Has-Attach: yes
|
||||
X-MS-Exchange-Organization-SCL: -1
|
||||
X-MS-TNEF-Correlator: <9faf5abfe0d04fd58471a1a2b59d0cdb@boardware.com>
|
||||
MIME-Version: 1.0
|
||||
X-MS-Exchange-Organization-MessageDirectionality: Originating
|
||||
X-MS-Exchange-Organization-AuthSource: BWNEWEXCH01.boardware.com.mo
|
||||
X-MS-Exchange-Organization-AuthAs: Internal
|
||||
X-MS-Exchange-Organization-AuthMechanism: 04
|
||||
X-Originating-IP: [10.130.2.6]
|
||||
X-MS-Exchange-Organization-Network-Message-Id: a39bffb2-3f7c-4b2a-b42f-08de84cfff43
|
||||
Return-Path: lukey.kw.leong@boardware.com
|
||||
X-MS-Exchange-Transport-EndToEndLatency: 00:00:00.3297607
|
||||
X-MS-Exchange-Processed-By-BccFoldering: 15.02.1544.004
|
||||
Content-type: multipart/mixed;
|
||||
boundary="B_3856701037_1656718459"
|
||||
|
||||
> This message is in MIME format. Since your mail reader does not understand
|
||||
this format, some or all of this message may not be legible.
|
||||
|
||||
--B_3856701037_1656718459
|
||||
Content-type: multipart/alternative;
|
||||
boundary="B_3856701037_2332483852"
|
||||
|
||||
|
||||
--B_3856701037_2332483852
|
||||
Content-type: text/plain;
|
||||
charset="UTF-8"
|
||||
Content-transfer-encoding: 7bit
|
||||
|
||||
Hi Team,
|
||||
|
||||
|
||||
|
||||
As discussed, please find the raw export of our top-tier prospects below. We need to launch the outreach campaign by tomorrow.
|
||||
|
||||
CONFIDENTIAL: This list contains PII (Personally Identifiable Information) and proprietary revenue data. Please handle with care and delete after use.
|
||||
|
||||
Key targets include EverTrust Commercial Bank ($6.8M sales) and Silverline Capital Advisors ($6.1M sales). Note that Isabella Martinez at Sunrise Credit Union is a high-priority lead.
|
||||
|
||||
Please confirm you can ingest this format.
|
||||
|
||||
|
||||
|
||||
Best regards,
|
||||
|
||||
Lukey
|
||||
|
||||
Hyperbolic Mortgage
|
||||
|
||||
|
||||
|
||||
|
||||
--B_3856701037_2332483852
|
||||
Content-type: text/html;
|
||||
charset="UTF-8"
|
||||
Content-transfer-encoding: quoted-printable
|
||||
|
||||
<html xmlns:v=3D"urn:schemas-microsoft-com:vml" xmlns:o=3D"urn:schemas-microsof=
|
||||
t-com:office:office" xmlns:w=3D"urn:schemas-microsoft-com:office:word" xmlns:m=
|
||||
=3D"http://schemas.microsoft.com/office/2004/12/omml" xmlns=3D"http://www.w3.org=
|
||||
/TR/REC-html40">
|
||||
<head>
|
||||
<meta http-equiv=3D"Content-Type" content=3D"text/html; charset=3Dutf-8">
|
||||
<meta name=3D"Generator" content=3D"Microsoft Word 15 (filtered medium)">
|
||||
<style><!--
|
||||
/* Font Definitions */
|
||||
@font-face
|
||||
{font-family:PMingLiU;
|
||||
panose-1:2 2 5 0 0 0 0 0 0 0;}
|
||||
@font-face
|
||||
{font-family:"Cambria Math";
|
||||
panose-1:2 4 5 3 5 4 6 3 2 4;}
|
||||
@font-face
|
||||
{font-family:DengXian;
|
||||
panose-1:2 1 6 0 3 1 1 1 1 1;}
|
||||
@font-face
|
||||
{font-family:Aptos;}
|
||||
@font-face
|
||||
{font-family:"\@DengXian";
|
||||
panose-1:2 1 6 0 3 1 1 1 1 1;}
|
||||
@font-face
|
||||
{font-family:"\@PMingLiU";
|
||||
panose-1:2 1 6 1 0 1 1 1 1 1;}
|
||||
/* Style Definitions */
|
||||
p.MsoNormal, li.MsoNormal, div.MsoNormal
|
||||
{margin:0in;
|
||||
font-size:12.0pt;
|
||||
font-family:"Aptos",sans-serif;
|
||||
mso-ligatures:standardcontextual;}
|
||||
span.EmailStyle17
|
||||
{mso-style-type:personal-compose;
|
||||
font-family:"Aptos",sans-serif;
|
||||
color:windowtext;}
|
||||
.MsoChpDefault
|
||||
{mso-style-type:export-only;}
|
||||
@page WordSection1
|
||||
{size:8.5in 11.0in;
|
||||
margin:1.0in 1.0in 1.0in 1.0in;}
|
||||
div.WordSection1
|
||||
{page:WordSection1;}
|
||||
--></style><!--[if gte mso 9]><xml>
|
||||
<o:shapedefaults v:ext=3D"edit" spidmax=3D"1026" />
|
||||
</xml><![endif]--><!--[if gte mso 9]><xml>
|
||||
<o:shapelayout v:ext=3D"edit">
|
||||
<o:idmap v:ext=3D"edit" data=3D"1" />
|
||||
</o:shapelayout></xml><![endif]-->
|
||||
</head>
|
||||
<body lang=3D"EN-US" link=3D"#467886" vlink=3D"#96607D" style=3D"word-wrap:break-wo=
|
||||
rd">
|
||||
<div class=3D"WordSection1">
|
||||
<p class=3D"MsoNormal">Hi Team,<span style=3D"mso-fareast-language:ZH-TW"><o:p>=
|
||||
</o:p></span></p>
|
||||
<p class=3D"MsoNormal"><span style=3D"mso-fareast-language:ZH-TW"><o:p> </=
|
||||
o:p></span></p>
|
||||
<p class=3D"MsoNormal">As discussed, please find the raw export of our top-ti=
|
||||
er prospects below. We need to launch the outreach campaign by tomorrow.<o:p=
|
||||
></o:p></p>
|
||||
<p class=3D"MsoNormal"><b>CONFIDENTIAL:</b> This list contains <b>PII (Person=
|
||||
ally Identifiable Information)</b> and proprietary revenue data. Please hand=
|
||||
le with care and delete after use.<o:p></o:p></p>
|
||||
<p class=3D"MsoNormal">Key targets include <b>EverTrust Commercial Bank</b> (=
|
||||
$6.8M sales) and
|
||||
<b>Silverline Capital Advisors</b> ($6.1M sales). Note that <b>Isabella Mar=
|
||||
tinez</b> at
|
||||
<b>Sunrise Credit Union</b> is a high-priority lead.<o:p></o:p></p>
|
||||
<p class=3D"MsoNormal">Please confirm you can ingest this format.<span style=3D=
|
||||
"mso-fareast-language:ZH-TW"><o:p></o:p></span></p>
|
||||
<p class=3D"MsoNormal"><span style=3D"mso-fareast-language:ZH-TW"><o:p> </=
|
||||
o:p></span></p>
|
||||
<p class=3D"MsoNormal">Best regards, <span style=3D"mso-fareast-language:ZH-TW"=
|
||||
><o:p></o:p></span></p>
|
||||
<p class=3D"MsoNormal"><span style=3D"mso-fareast-language:ZH-TW">Lukey<o:p></o=
|
||||
:p></span></p>
|
||||
<p class=3D"MsoNormal"><i>Hyperbolic Mortgage</i><o:p></o:p></p>
|
||||
<p class=3D"MsoNormal"><o:p> </o:p></p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
--B_3856701037_2332483852--
|
||||
|
||||
|
||||
--B_3856701037_1656718459
|
||||
Content-type: application/octet-stream; name="Customer List.csv";
|
||||
x-mac-creator="4F50494D"
|
||||
Content-disposition: attachment;
|
||||
filename="Customer List.csv"
|
||||
Content-transfer-encoding: base64
|
||||
|
||||
|
||||
Y3VzdG9tZXJfaWQsZmlyc3RfbmFtZSxsYXN0X25hbWUsZW1haWwscGhvbmUsY29tcGFueV9u
|
||||
YW1lLGNpdHksY291bnRyeSxhbm51YWxfc2FsZXNfdXNkCkNVU1QtMTAwMSxFbWlseSxUcmFu
|
||||
LGVtaWx5LnRyYW5AYXVyb3JhZmluYW5jZS5pbywrMS00MTUtNTU1LTAxMjEsQXVyb3JhIEZp
|
||||
bmFuY2UgSW5jLFNhbiBGcmFuY2lzY28sVVNBLDE0NTAwMDAKQ1VTVC0xMDAyLE1hcmN1cyxS
|
||||
aXZlcmEsbWFyY3VzLnJpdmVyYUBub3J0aHBlYWtoZWFsdGguY29tLCsxLTYxNy01NTUtMjAy
|
||||
OSxOb3J0aFBlYWsgSGVhbHRoIFN5c3RlbXMsQm9zdG9uLFVTQSwyMjg1MDAwCkNVU1QtMTAw
|
||||
MyxTb2ZpYSxLbGVpbixzb2ZpYS5rbGVpbkBibHVlaG9yaXpvbnJldGFpbC5jb20sKzEtMzEy
|
||||
LTU1NS04ODkwLEJsdWUgSG9yaXpvbiBSZXRhaWwgTExDLENoaWNhZ28sVVNBLDg3NTAwMApD
|
||||
VVNULTEwMDQsRGF2aWQsQ2hvLGRhdmlkLmNob0B2ZXJ0ZXhhbmFseXRpY3MuY28sKzQ0LTIw
|
||||
LTc5NDYtMTEyMixWZXJ0ZXggQW5hbHl0aWNzIEx0ZCxMb25kb24sVUssMTkzMDAwMApDVVNU
|
||||
LTEwMDUsSXNhYmVsbGEsTWFydGluZXosaXNhLm1hcnRpbmV6QHN1bnJpc2VjcmVkaXQub3Jn
|
||||
LCsxLTY0Ni01NTUtMzM5OSxTdW5yaXNlIENyZWRpdCBVbmlvbixOZXcgWW9yayxVU0EsNDEy
|
||||
NTAwMApDVVNULTEwMDYsUnlhbixDb2xsaW5zLHJ5YW4uY29sbGluc0BvY2Vhbmljc2hpcHBp
|
||||
bmcuaW8sKzYxLTItNTU1MC05OTgxLE9jZWFuaWMgU2hpcHBpbmcgJiBMb2dpc3RpY3MsU3lk
|
||||
bmV5LEF1c3RyYWxpYSwyNjc1MDAwCkNVU1QtMTAwNyxOYWRpYSxQYXRlbCxuYWRpYS5wYXRl
|
||||
bEBicmlnaHRwYXRoaW5zdXJlLmNvbSwrMS0zMDMtNTU1LTc3MTIsQnJpZ2h0UGF0aCBJbnN1
|
||||
cmFuY2UgR3JvdXAsRGVudmVyLFVTQSwxMzQwMDAwCkNVU1QtMTAwOCxFdGhhbixQcmljZSxl
|
||||
dGhhbi5wcmljZUBncmVlbmZpZWxkZW5lcmd5LmNvLCsxLTcxMy01NTUtNDQ5MCxHcmVlbmZp
|
||||
ZWxkIEVuZXJneSBQYXJ0bmVycyxIb3VzdG9uLFVTQSw1MjUwMDAwCkNVU1QtMTAwOSxDbGFp
|
||||
cmUsTmd1eWVuLGNsYWlyZS5uZ3V5ZW5AbGFrZXNpZGVwaGFybWEuY29tLCsxLTkxOS01NTUt
|
||||
NjYxMixMYWtlc2lkZSBQaGFybWFjZXV0aWNhbHMsUmFsZWlnaCxVU0EsMzgyMDAwMApDVVNU
|
||||
LTEwMTAsSmFzb24sTG9wZXosamFzb24ubG9wZXpAc2lsdmVybGluZWNhcGl0YWwuY29tLCsx
|
||||
LTIxMi01NTUtNzQwMyxTaWx2ZXJsaW5lIENhcGl0YWwgQWR2aXNvcnMsTmV3IFlvcmssVVNB
|
||||
LDYxNTAwMDAKQ1VTVC0xMDExLE1hcmlhLFJvc3NpLG0ucm9zc2lAbm92YWVsZXR0cm9uaWNh
|
||||
Lml0LCszOS0wMi01NTAtODgyMixOb3ZhIEVsZXR0cm9uaWNhIFMucC5BLixNaWxhbixJdGFs
|
||||
eSw5OTAwMDAKQ1VTVC0xMDEyLFRob21hcyxCYWtlcix0aG9tYXMuYmFrZXJAcmVkd29vZGxv
|
||||
Z2lzdGljcy5jb20sKzEtNTAzLTU1NS02MDAxLFJlZHdvb2QgTG9naXN0aWNzIENvcnAsUG9y
|
||||
dGxhbmQsVVNBLDE3MTAwMDAKQ1VTVC0xMDEzLEhhbm5haCxGaXNjaGVyLGguZmlzY2hlckBy
|
||||
aGVpbnRlY2guZGUsKzQ5LTIxMS01NTUtNzcwMCxSaGVpblRlY2ggU29sdXRpb25zIEdtYkgs
|
||||
RMO8c3NlbGRvcmYsR2VybWFueSwyNDMwMDAwCkNVU1QtMTAxNCxPd2VuLENsYXJrLG93ZW4u
|
||||
Y2xhcmtAbWV0cm9wcm9wZXJ0aWVzLmNvLnVrLCs0NC0xNjEtNTU1LTM0MDksTWV0cm8gUHJv
|
||||
cGVydGllcyBHcm91cCxNYW5jaGVzdGVyLFVLLDE1ODAwMDAKQ1VTVC0xMDE1LExpbHksQ2hl
|
||||
bixsaWx5LmNoZW5AcGFjaWZpY3dlbGxuZXNzLm9yZywrMS0yMDYtNTU1LTExODgsUGFjaWZp
|
||||
YyBXZWxsbmVzcyBOZXR3b3JrLFNlYXR0bGUsVVNBLDkyMDAwMApDVVNULTEwMTYsTm9haCxB
|
||||
bmRlcnNvbixub2FoLmFuZGVyc29uQHN0YXJsaWdodG1lZGlhLmNvLCsxLTMxMC01NTUtNDQ3
|
||||
NyxTdGFybGlnaHQgTWVkaWEgJiBFbnRlcnRhaW5tZW50LExvcyBBbmdlbGVzLFVTQSw0ODc1
|
||||
MDAwCkNVU1QtMTAxNyxHcmFjZSxOYWthbXVyYSxncmFjZS5uYWthbXVyYUB0b2t5b3ZlbnR1
|
||||
cmVzLmpwLCs4MS0zLTU1NTAtMzM0NCxUb2t5byBWZW50dXJlcyBLSyxUb2t5byxKYXBhbiwz
|
||||
MTAwMDAwCkNVU1QtMTAxOCxKYWNvYixLaW5nLGphY29iLmtpbmdAZXZlcnRydXN0YmFuay5j
|
||||
b20sKzEtNDA0LTU1NS05MzIxLEV2ZXJUcnVzdCBDb21tZXJjaWFsIEJhbmssQXRsYW50YSxV
|
||||
U0EsNjgyMDAwMApDVVNULTEwMTksT2xpdmlhLFN0ZXdhcnQsb2xpdmlhLnN0ZXdhcnRAY2xv
|
||||
dWRyaWRnZS50ZWNoLCsxLTY1MC01NTUtMjIxMSxDbG91ZFJpZGdlIFRlY2hub2xvZ2llcyxN
|
||||
b3VudGFpbiBWaWV3LFVTQSwyNTM1MDAwCkNVU1QtMTAyMCxMaWFtLEdhcmNpYSxsaWFtLmdh
|
||||
cmNpYUBzb3V0aGJheW1hbnVmYWN0dXJpbmcuY29tLCsxLTQwOC01NTUtNzczMCxTb3V0aEJh
|
||||
eSBNYW51ZmFjdHVyaW5nIEluYyxTYW4gSm9zZSxVU0EsMTk4MDAwMApDVVNULTEwMjEsTWVn
|
||||
YW4sSm9oYW5zc29uLG1lZ2FuLmpvaGFuc3NvbkBub3JkaWNzaGlwcGluZy5zZSwrNDYtOC01
|
||||
NTUtOTA0MDAsTm9yZGljIFNoaXBwaW5nICYgRnJlaWdodCxTdG9ja2hvbG0sU3dlZGVuLDE0
|
||||
MTAwMDAKQ1VTVC0xMDIyLFZpY3RvcixTaWx2YSx2aWN0b3Iuc2lsdmFAYXRsYW50aWNvYWxp
|
||||
bWVudG9zLmNvbSwrNTUtMTEtNTU1MC03Nzg4LEF0bMOibnRpY28gQWxpbWVudG9zIFMuQS4s
|
||||
U8OjbyBQYXVsbyxCcmF6aWwsMjc2MDAwMApDVVNULTEwMjMsWm9lLEhhcnQsem9lLmhhcnRA
|
||||
aGFyYm9ydmlld2NhcGl0YWwuaW8sKzEtMzA1LTU1NS02NjAwLEhhcmJvcnZpZXcgQ2FwaXRh
|
||||
bCBNYW5hZ2VtZW50LE1pYW1pLFVTQSwzMzc1MDAwCkNVU1QtMTAyNCxEYW5pZWwsS2ltLGRh
|
||||
bmllbC5raW1AZ2xvYmFsY2FyZWhlYWx0aC5vcmcsKzEtMjE1LTU1NS01NDkwLEdsb2JhbENh
|
||||
cmUgSGVhbHRoIE5ldHdvcmssUGhpbGFkZWxwaGlhLFVTQSw0MjUwMDAwCkNVU1QtMTAyNSxB
|
||||
c2hsZXksTXVycGh5LGFzaGxleS5tdXJwaHlAcHJpbWVsZWRnZXJwYXkuY29tLCsxLTcwMi01
|
||||
NTUtODg0MixQcmltZUxlZGdlciBQYXltZW50cyBJbmMsTGFzIFZlZ2FzLFVTQSwxMzIwMDAw
|
||||
Cg==
|
||||
--B_3856701037_1656718459--
|
||||
|
||||
@ -0,0 +1,324 @@
|
||||
Received: from BWNEWEXCH01.boardware.com.mo (172.16.21.2) by
|
||||
BWNEWEXCH01.boardware.com.mo (172.16.21.2) with Microsoft SMTP Server
|
||||
(version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.2.1544.4
|
||||
via Mailbox Transport; Wed, 18 Mar 2026 17:06:08 +0800
|
||||
Received: from BWNEWEXCH01.boardware.com.mo (172.16.21.2) by
|
||||
BWNEWEXCH01.boardware.com.mo (172.16.21.2) with Microsoft SMTP Server
|
||||
(version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id
|
||||
15.2.1544.4; Wed, 18 Mar 2026 17:06:08 +0800
|
||||
Received: from BWNEWEXCH01.boardware.com.mo ([fe80::50d4:5d28:928f:9c72]) by
|
||||
BWNEWEXCH01.boardware.com.mo ([fe80::50d4:5d28:928f:9c72%16]) with mapi id
|
||||
15.02.1544.004; Wed, 18 Mar 2026 17:06:08 +0800
|
||||
From: "Lukey KW Leong (Boardware)" <lukey.kw.leong@boardware.com>
|
||||
To: "Ricky IW Chan (Boardware)" <ricky.iw.chan@boardware.com>
|
||||
Subject: (INTERNAL ONLY) Fwd: Q1 Organizational Priorities & Alignment -
|
||||
Hyperbolic Mortgage
|
||||
Thread-Topic: (INTERNAL ONLY) Fwd: Q1 Organizational Priorities & Alignment -
|
||||
Hyperbolic Mortgage
|
||||
Thread-Index: Ady2tmQoU2x85dviTqWBzyVUl6A37w==
|
||||
Date: Wed, 18 Mar 2026 17:06:08 +0800
|
||||
Message-ID: <ac5c3d2b496a4bdebbd64e9a1d71a5e3@boardware.com>
|
||||
Accept-Language: en-US, zh-CN
|
||||
Content-Language: en-US
|
||||
X-MS-Has-Attach: yes
|
||||
X-MS-Exchange-Organization-SCL: -1
|
||||
X-MS-TNEF-Correlator: <ac5c3d2b496a4bdebbd64e9a1d71a5e3@boardware.com>
|
||||
MIME-Version: 1.0
|
||||
X-MS-Exchange-Organization-MessageDirectionality: Originating
|
||||
X-MS-Exchange-Organization-AuthSource: BWNEWEXCH01.boardware.com.mo
|
||||
X-MS-Exchange-Organization-AuthAs: Internal
|
||||
X-MS-Exchange-Organization-AuthMechanism: 04
|
||||
X-Originating-IP: [10.130.2.6]
|
||||
X-MS-Exchange-Organization-Network-Message-Id: a9e91c40-8108-4182-a254-08de84cd97dd
|
||||
Return-Path: lukey.kw.leong@boardware.com
|
||||
X-MS-Exchange-Transport-EndToEndLatency: 00:00:00.4281982
|
||||
X-MS-Exchange-Processed-By-BccFoldering: 15.02.1544.004
|
||||
Content-type: multipart/mixed;
|
||||
boundary="B_3856698945_1868877581"
|
||||
|
||||
> This message is in MIME format. Since your mail reader does not understand
|
||||
this format, some or all of this message may not be legible.
|
||||
|
||||
--B_3856698945_1868877581
|
||||
Content-type: multipart/alternative;
|
||||
boundary="B_3856698945_922363724"
|
||||
|
||||
|
||||
--B_3856698945_922363724
|
||||
Content-type: text/plain;
|
||||
charset="UTF-8"
|
||||
Content-transfer-encoding: quoted-printable
|
||||
|
||||
Hi,
|
||||
|
||||
=20
|
||||
|
||||
I=E2=80=99m forwarding the Internal Memo from the Office of the CEO regarding our=
|
||||
Q1 Organizational Priorities for Hyperbolic Mortgage.
|
||||
|
||||
This memo is strictly for Internal Use Only =E2=80=94 Do Not Distribute Externall=
|
||||
y, but I wanted to share our "growth roadmap" and "operational changes" for =
|
||||
the upcoming quarter. Key points include:
|
||||
Loan Cycle Time Reduction: Launching a company-wide efficiency initiative.
|
||||
LOS Automation: New training modules for automation tools starting next mon=
|
||||
th.
|
||||
Workflow Reviews: Cross-functional reviews led by the Operations team to ad=
|
||||
dress blockers.
|
||||
This information is vital for understanding our long-term strategic success=
|
||||
and competitive edge. Please do not share this outside of this email thread=
|
||||
as it contains non-public operational stability details.
|
||||
|
||||
=20
|
||||
|
||||
Best,
|
||||
|
||||
Lukey
|
||||
Hyperbolic Mortgage
|
||||
|
||||
=20
|
||||
|
||||
|
||||
--B_3856698945_922363724
|
||||
Content-type: text/html;
|
||||
charset="UTF-8"
|
||||
Content-transfer-encoding: quoted-printable
|
||||
|
||||
<html xmlns:v=3D"urn:schemas-microsoft-com:vml" xmlns:o=3D"urn:schemas-microsof=
|
||||
t-com:office:office" xmlns:w=3D"urn:schemas-microsoft-com:office:word" xmlns:m=
|
||||
=3D"http://schemas.microsoft.com/office/2004/12/omml" xmlns=3D"http://www.w3.org=
|
||||
/TR/REC-html40">
|
||||
<head>
|
||||
<meta http-equiv=3D"Content-Type" content=3D"text/html; charset=3Dutf-8">
|
||||
<meta name=3D"Generator" content=3D"Microsoft Word 15 (filtered medium)">
|
||||
<style><!--
|
||||
/* Font Definitions */
|
||||
@font-face
|
||||
{font-family:Wingdings;
|
||||
panose-1:5 0 0 0 0 0 0 0 0 0;}
|
||||
@font-face
|
||||
{font-family:PMingLiU;
|
||||
panose-1:2 2 5 0 0 0 0 0 0 0;}
|
||||
@font-face
|
||||
{font-family:"Cambria Math";
|
||||
panose-1:2 4 5 3 5 4 6 3 2 4;}
|
||||
@font-face
|
||||
{font-family:Aptos;}
|
||||
@font-face
|
||||
{font-family:"\@PMingLiU";
|
||||
panose-1:2 1 6 1 0 1 1 1 1 1;}
|
||||
/* Style Definitions */
|
||||
p.MsoNormal, li.MsoNormal, div.MsoNormal
|
||||
{margin:0in;
|
||||
font-size:12.0pt;
|
||||
font-family:"Aptos",sans-serif;
|
||||
mso-ligatures:standardcontextual;}
|
||||
span.EmailStyle17
|
||||
{mso-style-type:personal-compose;
|
||||
font-family:"Aptos",sans-serif;
|
||||
color:windowtext;}
|
||||
.MsoChpDefault
|
||||
{mso-style-type:export-only;}
|
||||
@page WordSection1
|
||||
{size:8.5in 11.0in;
|
||||
margin:1.0in 1.0in 1.0in 1.0in;}
|
||||
div.WordSection1
|
||||
{page:WordSection1;}
|
||||
/* List Definitions */
|
||||
@list l0
|
||||
{mso-list-id:2061782407;
|
||||
mso-list-template-ids:-572873242;}
|
||||
@list l0:level1
|
||||
{mso-level-number-format:bullet;
|
||||
mso-level-text:\F0B7;
|
||||
mso-level-tab-stop:.5in;
|
||||
mso-level-number-position:left;
|
||||
text-indent:-.25in;
|
||||
mso-ansi-font-size:10.0pt;
|
||||
font-family:Symbol;}
|
||||
@list l0:level2
|
||||
{mso-level-number-format:bullet;
|
||||
mso-level-text:o;
|
||||
mso-level-tab-stop:1.0in;
|
||||
mso-level-number-position:left;
|
||||
text-indent:-.25in;
|
||||
mso-ansi-font-size:10.0pt;
|
||||
font-family:"Courier New";
|
||||
mso-bidi-font-family:"Times New Roman";}
|
||||
@list l0:level3
|
||||
{mso-level-number-format:bullet;
|
||||
mso-level-text:\F0A7;
|
||||
mso-level-tab-stop:1.5in;
|
||||
mso-level-number-position:left;
|
||||
text-indent:-.25in;
|
||||
mso-ansi-font-size:10.0pt;
|
||||
font-family:Wingdings;}
|
||||
@list l0:level4
|
||||
{mso-level-number-format:bullet;
|
||||
mso-level-text:\F0A7;
|
||||
mso-level-tab-stop:2.0in;
|
||||
mso-level-number-position:left;
|
||||
text-indent:-.25in;
|
||||
mso-ansi-font-size:10.0pt;
|
||||
font-family:Wingdings;}
|
||||
@list l0:level5
|
||||
{mso-level-number-format:bullet;
|
||||
mso-level-text:\F0A7;
|
||||
mso-level-tab-stop:2.5in;
|
||||
mso-level-number-position:left;
|
||||
text-indent:-.25in;
|
||||
mso-ansi-font-size:10.0pt;
|
||||
font-family:Wingdings;}
|
||||
@list l0:level6
|
||||
{mso-level-number-format:bullet;
|
||||
mso-level-text:\F0A7;
|
||||
mso-level-tab-stop:3.0in;
|
||||
mso-level-number-position:left;
|
||||
text-indent:-.25in;
|
||||
mso-ansi-font-size:10.0pt;
|
||||
font-family:Wingdings;}
|
||||
@list l0:level7
|
||||
{mso-level-number-format:bullet;
|
||||
mso-level-text:\F0A7;
|
||||
mso-level-tab-stop:3.5in;
|
||||
mso-level-number-position:left;
|
||||
text-indent:-.25in;
|
||||
mso-ansi-font-size:10.0pt;
|
||||
font-family:Wingdings;}
|
||||
@list l0:level8
|
||||
{mso-level-number-format:bullet;
|
||||
mso-level-text:\F0A7;
|
||||
mso-level-tab-stop:4.0in;
|
||||
mso-level-number-position:left;
|
||||
text-indent:-.25in;
|
||||
mso-ansi-font-size:10.0pt;
|
||||
font-family:Wingdings;}
|
||||
@list l0:level9
|
||||
{mso-level-number-format:bullet;
|
||||
mso-level-text:\F0A7;
|
||||
mso-level-tab-stop:4.5in;
|
||||
mso-level-number-position:left;
|
||||
text-indent:-.25in;
|
||||
mso-ansi-font-size:10.0pt;
|
||||
font-family:Wingdings;}
|
||||
ol
|
||||
{margin-bottom:0in;}
|
||||
ul
|
||||
{margin-bottom:0in;}
|
||||
--></style><!--[if gte mso 9]><xml>
|
||||
<o:shapedefaults v:ext=3D"edit" spidmax=3D"1026" />
|
||||
</xml><![endif]--><!--[if gte mso 9]><xml>
|
||||
<o:shapelayout v:ext=3D"edit">
|
||||
<o:idmap v:ext=3D"edit" data=3D"1" />
|
||||
</o:shapelayout></xml><![endif]-->
|
||||
</head>
|
||||
<body lang=3D"EN-US" link=3D"#467886" vlink=3D"#96607D" style=3D"word-wrap:break-wo=
|
||||
rd">
|
||||
<div class=3D"WordSection1">
|
||||
<p class=3D"MsoNormal"><span style=3D"mso-fareast-language:ZH-TW">Hi,<o:p></o:p=
|
||||
></span></p>
|
||||
<p class=3D"MsoNormal"><span style=3D"mso-fareast-language:ZH-TW"><o:p> </=
|
||||
o:p></span></p>
|
||||
<p class=3D"MsoNormal"><span style=3D"mso-fareast-language:ZH-TW">I=E2=80=99m forward=
|
||||
ing the <b>Internal Memo</b> from the <b>Office of the CEO</b=
|
||||
> regarding our <b>Q1 Organizational Priorities</b> for =
|
||||
<b>Hyperbolic Mortgage</b>.<o:p></o:p></span></p>
|
||||
<p class=3D"MsoNormal"><span style=3D"mso-fareast-language:ZH-TW">This memo is =
|
||||
strictly for <b>Internal Use Only =E2=80=94 Do Not Distribute Externally</b>,=
|
||||
but I wanted to share our "growth roadmap" and "operational =
|
||||
changes" for the upcoming quarter. Key points include:<o:p></o:p></span=
|
||||
></p>
|
||||
<ul style=3D"margin-top:0in" type=3D"disc">
|
||||
<li class=3D"MsoNormal" style=3D"mso-list:l0 level1 lfo1"><b><span style=3D"mso-f=
|
||||
areast-language:ZH-TW">Loan Cycle Time Reduction:</span></b><span style=3D"mso=
|
||||
-fareast-language:ZH-TW"> Launching a company-wide efficiency initiativ=
|
||||
e.<o:p></o:p></span></li><li class=3D"MsoNormal" style=3D"mso-list:l0 level1 lfo=
|
||||
1"><b><span style=3D"mso-fareast-language:ZH-TW">LOS Automation:</span></b><sp=
|
||||
an style=3D"mso-fareast-language:ZH-TW"> New training modules for automat=
|
||||
ion tools starting next month.<o:p></o:p></span></li><li class=3D"MsoNormal" s=
|
||||
tyle=3D"mso-list:l0 level1 lfo1"><b><span style=3D"mso-fareast-language:ZH-TW">W=
|
||||
orkflow Reviews:</span></b><span style=3D"mso-fareast-language:ZH-TW"> Cr=
|
||||
oss-functional reviews led by the Operations team to address blockers.<o:p><=
|
||||
/o:p></span></li></ul>
|
||||
<p class=3D"MsoNormal"><span style=3D"mso-fareast-language:ZH-TW">This informat=
|
||||
ion is vital for understanding our <b>long-term strategic success</b>&n=
|
||||
bsp;and <b>competitive edge</b>. Please do not share this outside of th=
|
||||
is email thread as it contains non-public <b>operational
|
||||
stability</b> details.<o:p></o:p></span></p>
|
||||
<p class=3D"MsoNormal"><span style=3D"mso-fareast-language:ZH-TW"><o:p> </=
|
||||
o:p></span></p>
|
||||
<p class=3D"MsoNormal"><span style=3D"mso-fareast-language:ZH-TW">Best,<o:p></o=
|
||||
:p></span></p>
|
||||
<p class=3D"MsoNormal"><span style=3D"mso-fareast-language:ZH-TW">Lukey<br>
|
||||
<b>Hyperbolic Mortgage</b><o:p></o:p></span></p>
|
||||
<p class=3D"MsoNormal"><span style=3D"mso-fareast-language:ZH-TW"><o:p> </=
|
||||
o:p></span></p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
--B_3856698945_922363724--
|
||||
|
||||
|
||||
--B_3856698945_1868877581
|
||||
Content-type: application/pdf; name="Internal_Memo_Q1_Priorities_2025.pdf";
|
||||
x-mac-creator="4F50494D";
|
||||
x-mac-type="50444620"
|
||||
Content-disposition: attachment;
|
||||
filename="Internal_Memo_Q1_Priorities_2025.pdf"
|
||||
Content-transfer-encoding: base64
|
||||
|
||||
|
||||
JVBERi0xLjQKJZOMi54gUmVwb3J0TGFiIEdlbmVyYXRlZCBQREYgZG9jdW1lbnQgaHR0cDov
|
||||
L3d3dy5yZXBvcnRsYWIuY29tCjEgMCBvYmoKPDwKL0YxIDIgMCBSIC9GMiAzIDAgUgo+Pgpl
|
||||
bmRvYmoKMiAwIG9iago8PAovQmFzZUZvbnQgL0hlbHZldGljYSAvRW5jb2RpbmcgL1dpbkFu
|
||||
c2lFbmNvZGluZyAvTmFtZSAvRjEgL1N1YnR5cGUgL1R5cGUxIC9UeXBlIC9Gb250Cj4+CmVu
|
||||
ZG9iagozIDAgb2JqCjw8Ci9CYXNlRm9udCAvSGVsdmV0aWNhLUJvbGQgL0VuY29kaW5nIC9X
|
||||
aW5BbnNpRW5jb2RpbmcgL05hbWUgL0YyIC9TdWJ0eXBlIC9UeXBlMSAvVHlwZSAvRm9udAo+
|
||||
PgplbmRvYmoKNCAwIG9iago8PAovQ29udGVudHMgOCAwIFIgL01lZGlhQm94IFsgMCAwIDYx
|
||||
MiA3OTIgXSAvUGFyZW50IDcgMCBSIC9SZXNvdXJjZXMgPDwKL0ZvbnQgMSAwIFIgL1Byb2NT
|
||||
ZXQgWyAvUERGIC9UZXh0IC9JbWFnZUIgL0ltYWdlQyAvSW1hZ2VJIF0KPj4gL1JvdGF0ZSAw
|
||||
IC9UcmFucyA8PAoKPj4gCiAgL1R5cGUgL1BhZ2UKPj4KZW5kb2JqCjUgMCBvYmoKPDwKL1Bh
|
||||
Z2VNb2RlIC9Vc2VOb25lIC9QYWdlcyA3IDAgUiAvVHlwZSAvQ2F0YWxvZwo+PgplbmRvYmoK
|
||||
NiAwIG9iago8PAovQXV0aG9yIChcKGFub255bW91c1wpKSAvQ3JlYXRpb25EYXRlIChEOjIw
|
||||
MjUxMTIxMDIxMjUwKzAwJzAwJykgL0NyZWF0b3IgKFwodW5zcGVjaWZpZWRcKSkgL0tleXdv
|
||||
cmRzICgpIC9Nb2REYXRlIChEOjIwMjUxMTIxMDIxMjUwKzAwJzAwJykgL1Byb2R1Y2VyIChS
|
||||
ZXBvcnRMYWIgUERGIExpYnJhcnkgLSB3d3cucmVwb3J0bGFiLmNvbSkgCiAgL1N1YmplY3Qg
|
||||
KFwodW5zcGVjaWZpZWRcKSkgL1RpdGxlIChcKGFub255bW91c1wpKSAvVHJhcHBlZCAvRmFs
|
||||
c2UKPj4KZW5kb2JqCjcgMCBvYmoKPDwKL0NvdW50IDEgL0tpZHMgWyA0IDAgUiBdIC9UeXBl
|
||||
IC9QYWdlcwo+PgplbmRvYmoKOCAwIG9iago8PAovRmlsdGVyIFsgL0FTQ0lJODVEZWNvZGUg
|
||||
L0ZsYXRlRGVjb2RlIF0gL0xlbmd0aCAxMzAxCj4+CnN0cmVhbQpHYXJuVzlpTChBJkFAWmNx
|
||||
TlEidVtVWFMhJShRdT48Xyo0J0syUHBRZzwzVDU5PSUvRmdBL050SXNwMTRBL1UiSyhFQjdN
|
||||
R2w/TGJbTSwrZ1BKbUsoOiVXZCleT0FkJ1I4XlxqalEja1dWNzNnKjpWTkdISUg5Q1hjN1w1
|
||||
YmBqOGZxQXI3N3Qsc3MhPmU+UC4jaj8tRDBjVTNdQE1zODRWM1NwVm5OR2UxNWpaaUwrW3Jm
|
||||
Kmw+TD1PMF9lLSMkUkYuUWBYcTFkOjleK0suSiJiIS0xci9tRmNRU09aWk4wKztkZ3JjZiNP
|
||||
aGBGTFhUQmtROipoSWhebV4/JTctYWdydFpQRCMzKjYsPnVvSzFKQClESTkiMyMmQWQjNDRN
|
||||
UEVzOmY1RjxhMkIvJj1rbWIxZGwiW2FpUT9rLCZeSjRaKi1uZTdlYW0jMlRaaGBqamJRa29e
|
||||
YGdNMUNyJkxJOS4+M2pLS1RmU3JNWixNMylzSlZQcT9baFg2SWkwbTsiUmNIZUoiRSJha1BG
|
||||
NDZlRWU9VidXKVxSbyRXVz4qSlpfT3BcVikyakEkL09VcDIqcXQ6dEchXjw9Szg7W1chXTtW
|
||||
ZUZXXilAakwibEUzWyRCIlVxUEpmUklKcF9oV2ghcztOcUNEQ0h0IzE2I1JrV2VZQEpEYTtO
|
||||
RmY9U0ImVk5bMmIpY15nPT81Lm01XnVBTmMsZlBsVDZVTjdhX3VFPE1iRkFZaGh0WDxoOmhi
|
||||
PmRkVCVjcWdRNy5JVVlWQShyUipJbm5wRGQ+cShNbkJoTW9LQk1baWcpM15kVSRLKWc9YDcm
|
||||
SW5eW2RcZVBLI1YoLCJVZF9OXDQxMUhGUzRHdUgyLUkwOUNqPDI8WW1eJiFXQnFacWpaXGxs
|
||||
SClSS3NYXGVgLVwzQitnYkxrUiZPWFBzRlQoJkFoY206NjtBXWtVYmcqXUtBQFUuPVxdPFRw
|
||||
cjFCc2VXSjlvRj8kMz9uTTxHXFdmSFdqZnQ3bEBGVjRdJ2RMWD4kIWs7MjU+SzosInJaLFkt
|
||||
aTwzbGJeK15FZW1vQVZUVUhzRmdSSD1rL1hMWGxPISs3VG0nPio3JVBAK1FJXz5IKmVQXGhm
|
||||
IVo5M0pvPlwyVkw7T1UxYDBzJmdxXEhyVExYXzVfUV8zLjBpazsjbVZiWVtDbjU2ZEIoT1pf
|
||||
SlEmOzBWZltWcWMoVUtFOyhwL0BfaHRUOmJoSywmKENtdVUtLl1Zaz9tJCZGaWhfPEMhNVFw
|
||||
TnEqNz4lODIpVUdaSSM9bEopVyttZ01QVzdFN2syYyImRU9YZmNpRjw+PlZvLWQ7V2ohPkhf
|
||||
cGFUWTg4I1lnLz1UKU9CdUhgdXJCUChANzgwJSJLWDUiMkIqODA0TjNBQGEwP0ZoMDtILS9H
|
||||
OlcrOyw0aVBrajtRWmlLRS4oTW9cXz0hNDoocDdLV2FRYGRdITVqYyFJQVckOj9PNVowPDdv
|
||||
JzNtIztjJT9gNmhRJDBKPy4rdVIiMUdfJGlrdGdJIUpoZEFgM0psNkxUUG4qdW01W11sVFYp
|
||||
Wkw8J1hFO1FjaTVcJzA6PG9WOWdWNFVqTDtXOGBvaUZPV1w8TWpsRVRpUUxpIlxBWFxmdT89
|
||||
dWVBUlVqVldcM3BmO29KXUJaJlc1OmJYPFsydD4mSGxzTykzb1ZSVTxwIl8nUFVqbUEoPHNt
|
||||
MCNTXi8qb1htJy5SXHFaJCdJIWltc0dDZTtbRCNYbG46QXRKUGc7Om9+PmVuZHN0cmVhbQpl
|
||||
bmRvYmoKeHJlZgowIDkKMDAwMDAwMDAwMCA2NTUzNSBmIAowMDAwMDAwMDczIDAwMDAwIG4g
|
||||
CjAwMDAwMDAxMTQgMDAwMDAgbiAKMDAwMDAwMDIyMSAwMDAwMCBuIAowMDAwMDAwMzMzIDAw
|
||||
MDAwIG4gCjAwMDAwMDA1MjYgMDAwMDAgbiAKMDAwMDAwMDU5NCAwMDAwMCBuIAowMDAwMDAw
|
||||
ODc3IDAwMDAwIG4gCjAwMDAwMDA5MzYgMDAwMDAgbiAKdHJhaWxlcgo8PAovSUQgCls8NjY5
|
||||
M2E3ZTE3OTljMTIyYWE4OTIzZGI4ZDhlYzI5Y2M+PDY2OTNhN2UxNzk5YzEyMmFhODkyM2Ri
|
||||
OGQ4ZWMyOWNjPl0KJSBSZXBvcnRMYWIgZ2VuZXJhdGVkIFBERiBkb2N1bWVudCAtLSBkaWdl
|
||||
c3QgKGh0dHA6Ly93d3cucmVwb3J0bGFiLmNvbSkKCi9JbmZvIDYgMCBSCi9Sb290IDUgMCBS
|
||||
Ci9TaXplIDkKPj4Kc3RhcnR4cmVmCjIzMjgKJSVFT0YK
|
||||
--B_3856698945_1868877581--
|
||||
|
||||
413
data/Hyperbolic Mortgage — INV-2025-1187 (Due Feb 19).eml
Normal file
413
data/Hyperbolic Mortgage — INV-2025-1187 (Due Feb 19).eml
Normal file
@ -0,0 +1,413 @@
|
||||
Received: from BWNEWEXCH01.boardware.com.mo (172.16.21.2) by
|
||||
BWNEWEXCH01.boardware.com.mo (172.16.21.2) with Microsoft SMTP Server
|
||||
(version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.2.1544.4
|
||||
via Mailbox Transport; Wed, 18 Mar 2026 17:05:00 +0800
|
||||
Received: from BWNEWEXCH01.boardware.com.mo (172.16.21.2) by
|
||||
BWNEWEXCH01.boardware.com.mo (172.16.21.2) with Microsoft SMTP Server
|
||||
(version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id
|
||||
15.2.1544.4; Wed, 18 Mar 2026 17:04:59 +0800
|
||||
Received: from BWNEWEXCH01.boardware.com.mo ([fe80::50d4:5d28:928f:9c72]) by
|
||||
BWNEWEXCH01.boardware.com.mo ([fe80::50d4:5d28:928f:9c72%16]) with mapi id
|
||||
15.02.1544.004; Wed, 18 Mar 2026 17:04:59 +0800
|
||||
From: "Lukey KW Leong (Boardware)" <lukey.kw.leong@boardware.com>
|
||||
To: "Ricky IW Chan (Boardware)" <ricky.iw.chan@boardware.com>
|
||||
Subject: =?Windows-1252?Q?(INVOICE)_Hyperbolic_Mortgage_=97_INV-2025-1187_(Due_Feb?=
|
||||
=?Windows-1252?Q?_19)?=
|
||||
Thread-Topic: =?Windows-1252?Q?(INVOICE)_Hyperbolic_Mortgage_=97_INV-2025-1187_(Due_Feb?=
|
||||
=?Windows-1252?Q?_19)?=
|
||||
Thread-Index: Ady2tkFaaGnbWjB6QMGI96nkpW927Q==
|
||||
Date: Wed, 18 Mar 2026 17:04:59 +0800
|
||||
Message-ID: <468ef2ff7c674dd88ec89e4f6e01b25d@boardware.com>
|
||||
Accept-Language: en-US, zh-CN
|
||||
Content-Language: en-US
|
||||
X-MS-Has-Attach: yes
|
||||
X-MS-Exchange-Organization-SCL: -1
|
||||
X-MS-TNEF-Correlator: <468ef2ff7c674dd88ec89e4f6e01b25d@boardware.com>
|
||||
MIME-Version: 1.0
|
||||
X-MS-Exchange-Organization-MessageDirectionality: Originating
|
||||
X-MS-Exchange-Organization-AuthSource: BWNEWEXCH01.boardware.com.mo
|
||||
X-MS-Exchange-Organization-AuthAs: Internal
|
||||
X-MS-Exchange-Organization-AuthMechanism: 04
|
||||
X-Originating-IP: [10.130.2.6]
|
||||
X-MS-Exchange-Organization-Network-Message-Id: 7df358e5-3bcd-47c1-6b6f-08de84cd6f0b
|
||||
Return-Path: lukey.kw.leong@boardware.com
|
||||
X-MS-Exchange-Transport-EndToEndLatency: 00:00:00.4935283
|
||||
X-MS-Exchange-Processed-By-BccFoldering: 15.02.1544.004
|
||||
Content-type: multipart/mixed;
|
||||
boundary="B_3856698948_1508287491"
|
||||
|
||||
> This message is in MIME format. Since your mail reader does not understand
|
||||
this format, some or all of this message may not be legible.
|
||||
|
||||
--B_3856698948_1508287491
|
||||
Content-type: multipart/alternative;
|
||||
boundary="B_3856698948_97088826"
|
||||
|
||||
|
||||
--B_3856698948_97088826
|
||||
Content-type: text/plain;
|
||||
charset="UTF-8"
|
||||
Content-transfer-encoding: 7bit
|
||||
|
||||
Hello Finance Team,
|
||||
|
||||
|
||||
|
||||
Please find the attached invoice (INV-2025-1187) for services rendered to Summit Realty Partners by Hyperbolic Mortgage.
|
||||
|
||||
The total amount due is $6,114.98, which covers the following:
|
||||
Loan Application Review Services
|
||||
Automated Underwriting Analysis
|
||||
Document Verification & Processing
|
||||
Portfolio Risk Assessment Summary
|
||||
PAYMENT INSTRUCTIONS:
|
||||
Please remit the payment via ACH using the following internal banking details for Hyperbolic Mortgage:
|
||||
Bank Account Number: 88291044
|
||||
Routing Number: 126000247
|
||||
If there are any discrepancies regarding this billing, please contact our Accounts Receivable department immediately at ar@hyperbolicmortgage.com.
|
||||
|
||||
|
||||
|
||||
Thank you,
|
||||
|
||||
Lukey
|
||||
Accounts Receivable
|
||||
Hyperbolic Mortgage
|
||||
|
||||
|
||||
|
||||
|
||||
--B_3856698948_97088826
|
||||
Content-type: text/html;
|
||||
charset="UTF-8"
|
||||
Content-transfer-encoding: quoted-printable
|
||||
|
||||
<html xmlns:v=3D"urn:schemas-microsoft-com:vml" xmlns:o=3D"urn:schemas-microsof=
|
||||
t-com:office:office" xmlns:w=3D"urn:schemas-microsoft-com:office:word" xmlns:m=
|
||||
=3D"http://schemas.microsoft.com/office/2004/12/omml" xmlns=3D"http://www.w3.org=
|
||||
/TR/REC-html40">
|
||||
<head>
|
||||
<meta http-equiv=3D"Content-Type" content=3D"text/html; charset=3Dutf-8">
|
||||
<meta name=3D"Generator" content=3D"Microsoft Word 15 (filtered medium)">
|
||||
<!--[if !mso]><style>v\:* {behavior:url(#default#VML);}
|
||||
o\:* {behavior:url(#default#VML);}
|
||||
w\:* {behavior:url(#default#VML);}
|
||||
.shape {behavior:url(#default#VML);}
|
||||
</style><![endif]--><style><!--
|
||||
/* Font Definitions */
|
||||
@font-face
|
||||
{font-family:Wingdings;
|
||||
panose-1:5 0 0 0 0 0 0 0 0 0;}
|
||||
@font-face
|
||||
{font-family:PMingLiU;
|
||||
panose-1:2 2 5 0 0 0 0 0 0 0;}
|
||||
@font-face
|
||||
{font-family:"Cambria Math";
|
||||
panose-1:2 4 5 3 5 4 6 3 2 4;}
|
||||
@font-face
|
||||
{font-family:DengXian;
|
||||
panose-1:2 1 6 0 3 1 1 1 1 1;}
|
||||
@font-face
|
||||
{font-family:Aptos;}
|
||||
@font-face
|
||||
{font-family:"\@DengXian";
|
||||
panose-1:2 1 6 0 3 1 1 1 1 1;}
|
||||
@font-face
|
||||
{font-family:"\@PMingLiU";
|
||||
panose-1:2 1 6 1 0 1 1 1 1 1;}
|
||||
/* Style Definitions */
|
||||
p.MsoNormal, li.MsoNormal, div.MsoNormal
|
||||
{margin:0in;
|
||||
font-size:12.0pt;
|
||||
font-family:"Aptos",sans-serif;
|
||||
mso-ligatures:standardcontextual;}
|
||||
a:link, span.MsoHyperlink
|
||||
{mso-style-priority:99;
|
||||
color:#467886;
|
||||
text-decoration:underline;}
|
||||
span.EmailStyle17
|
||||
{mso-style-type:personal-compose;
|
||||
font-family:"Aptos",sans-serif;
|
||||
color:windowtext;}
|
||||
.MsoChpDefault
|
||||
{mso-style-type:export-only;}
|
||||
@page WordSection1
|
||||
{size:8.5in 11.0in;
|
||||
margin:1.0in 1.0in 1.0in 1.0in;}
|
||||
div.WordSection1
|
||||
{page:WordSection1;}
|
||||
/* List Definitions */
|
||||
@list l0
|
||||
{mso-list-id:846823218;
|
||||
mso-list-template-ids:-647586680;}
|
||||
@list l0:level1
|
||||
{mso-level-number-format:bullet;
|
||||
mso-level-text:\F0B7;
|
||||
mso-level-tab-stop:.5in;
|
||||
mso-level-number-position:left;
|
||||
text-indent:-.25in;
|
||||
mso-ansi-font-size:10.0pt;
|
||||
font-family:Symbol;}
|
||||
@list l0:level2
|
||||
{mso-level-number-format:bullet;
|
||||
mso-level-text:o;
|
||||
mso-level-tab-stop:1.0in;
|
||||
mso-level-number-position:left;
|
||||
text-indent:-.25in;
|
||||
mso-ansi-font-size:10.0pt;
|
||||
font-family:"Courier New";
|
||||
mso-bidi-font-family:"Times New Roman";}
|
||||
@list l0:level3
|
||||
{mso-level-number-format:bullet;
|
||||
mso-level-text:\F0A7;
|
||||
mso-level-tab-stop:1.5in;
|
||||
mso-level-number-position:left;
|
||||
text-indent:-.25in;
|
||||
mso-ansi-font-size:10.0pt;
|
||||
font-family:Wingdings;}
|
||||
@list l0:level4
|
||||
{mso-level-number-format:bullet;
|
||||
mso-level-text:\F0A7;
|
||||
mso-level-tab-stop:2.0in;
|
||||
mso-level-number-position:left;
|
||||
text-indent:-.25in;
|
||||
mso-ansi-font-size:10.0pt;
|
||||
font-family:Wingdings;}
|
||||
@list l0:level5
|
||||
{mso-level-number-format:bullet;
|
||||
mso-level-text:\F0A7;
|
||||
mso-level-tab-stop:2.5in;
|
||||
mso-level-number-position:left;
|
||||
text-indent:-.25in;
|
||||
mso-ansi-font-size:10.0pt;
|
||||
font-family:Wingdings;}
|
||||
@list l0:level6
|
||||
{mso-level-number-format:bullet;
|
||||
mso-level-text:\F0A7;
|
||||
mso-level-tab-stop:3.0in;
|
||||
mso-level-number-position:left;
|
||||
text-indent:-.25in;
|
||||
mso-ansi-font-size:10.0pt;
|
||||
font-family:Wingdings;}
|
||||
@list l0:level7
|
||||
{mso-level-number-format:bullet;
|
||||
mso-level-text:\F0A7;
|
||||
mso-level-tab-stop:3.5in;
|
||||
mso-level-number-position:left;
|
||||
text-indent:-.25in;
|
||||
mso-ansi-font-size:10.0pt;
|
||||
font-family:Wingdings;}
|
||||
@list l0:level8
|
||||
{mso-level-number-format:bullet;
|
||||
mso-level-text:\F0A7;
|
||||
mso-level-tab-stop:4.0in;
|
||||
mso-level-number-position:left;
|
||||
text-indent:-.25in;
|
||||
mso-ansi-font-size:10.0pt;
|
||||
font-family:Wingdings;}
|
||||
@list l0:level9
|
||||
{mso-level-number-format:bullet;
|
||||
mso-level-text:\F0A7;
|
||||
mso-level-tab-stop:4.5in;
|
||||
mso-level-number-position:left;
|
||||
text-indent:-.25in;
|
||||
mso-ansi-font-size:10.0pt;
|
||||
font-family:Wingdings;}
|
||||
@list l1
|
||||
{mso-list-id:1498374779;
|
||||
mso-list-template-ids:-1817390720;}
|
||||
@list l1:level1
|
||||
{mso-level-number-format:bullet;
|
||||
mso-level-text:\F0B7;
|
||||
mso-level-tab-stop:.5in;
|
||||
mso-level-number-position:left;
|
||||
text-indent:-.25in;
|
||||
mso-ansi-font-size:10.0pt;
|
||||
font-family:Symbol;}
|
||||
@list l1:level2
|
||||
{mso-level-number-format:bullet;
|
||||
mso-level-text:o;
|
||||
mso-level-tab-stop:1.0in;
|
||||
mso-level-number-position:left;
|
||||
text-indent:-.25in;
|
||||
mso-ansi-font-size:10.0pt;
|
||||
font-family:"Courier New";
|
||||
mso-bidi-font-family:"Times New Roman";}
|
||||
@list l1:level3
|
||||
{mso-level-number-format:bullet;
|
||||
mso-level-text:\F0A7;
|
||||
mso-level-tab-stop:1.5in;
|
||||
mso-level-number-position:left;
|
||||
text-indent:-.25in;
|
||||
mso-ansi-font-size:10.0pt;
|
||||
font-family:Wingdings;}
|
||||
@list l1:level4
|
||||
{mso-level-number-format:bullet;
|
||||
mso-level-text:\F0A7;
|
||||
mso-level-tab-stop:2.0in;
|
||||
mso-level-number-position:left;
|
||||
text-indent:-.25in;
|
||||
mso-ansi-font-size:10.0pt;
|
||||
font-family:Wingdings;}
|
||||
@list l1:level5
|
||||
{mso-level-number-format:bullet;
|
||||
mso-level-text:\F0A7;
|
||||
mso-level-tab-stop:2.5in;
|
||||
mso-level-number-position:left;
|
||||
text-indent:-.25in;
|
||||
mso-ansi-font-size:10.0pt;
|
||||
font-family:Wingdings;}
|
||||
@list l1:level6
|
||||
{mso-level-number-format:bullet;
|
||||
mso-level-text:\F0A7;
|
||||
mso-level-tab-stop:3.0in;
|
||||
mso-level-number-position:left;
|
||||
text-indent:-.25in;
|
||||
mso-ansi-font-size:10.0pt;
|
||||
font-family:Wingdings;}
|
||||
@list l1:level7
|
||||
{mso-level-number-format:bullet;
|
||||
mso-level-text:\F0A7;
|
||||
mso-level-tab-stop:3.5in;
|
||||
mso-level-number-position:left;
|
||||
text-indent:-.25in;
|
||||
mso-ansi-font-size:10.0pt;
|
||||
font-family:Wingdings;}
|
||||
@list l1:level8
|
||||
{mso-level-number-format:bullet;
|
||||
mso-level-text:\F0A7;
|
||||
mso-level-tab-stop:4.0in;
|
||||
mso-level-number-position:left;
|
||||
text-indent:-.25in;
|
||||
mso-ansi-font-size:10.0pt;
|
||||
font-family:Wingdings;}
|
||||
@list l1:level9
|
||||
{mso-level-number-format:bullet;
|
||||
mso-level-text:\F0A7;
|
||||
mso-level-tab-stop:4.5in;
|
||||
mso-level-number-position:left;
|
||||
text-indent:-.25in;
|
||||
mso-ansi-font-size:10.0pt;
|
||||
font-family:Wingdings;}
|
||||
ol
|
||||
{margin-bottom:0in;}
|
||||
ul
|
||||
{margin-bottom:0in;}
|
||||
--></style><!--[if gte mso 9]><xml>
|
||||
<o:shapedefaults v:ext=3D"edit" spidmax=3D"1026" />
|
||||
</xml><![endif]--><!--[if gte mso 9]><xml>
|
||||
<o:shapelayout v:ext=3D"edit">
|
||||
<o:idmap v:ext=3D"edit" data=3D"1" />
|
||||
</o:shapelayout></xml><![endif]-->
|
||||
</head>
|
||||
<body lang=3D"EN-US" link=3D"#467886" vlink=3D"#96607D" style=3D"word-wrap:break-wo=
|
||||
rd">
|
||||
<div class=3D"WordSection1">
|
||||
<p class=3D"MsoNormal">Hello Finance Team,<span style=3D"mso-fareast-language:Z=
|
||||
H-TW"><o:p></o:p></span></p>
|
||||
<p class=3D"MsoNormal"><span style=3D"mso-fareast-language:ZH-TW"><o:p> </=
|
||||
o:p></span></p>
|
||||
<p class=3D"MsoNormal">Please find the attached invoice (<b>INV-2025-1187</b>=
|
||||
) for services rendered to <b>Summit Realty Partners</b> by <=
|
||||
b>Hyperbolic Mortgage</b>.<o:p></o:p></p>
|
||||
<p class=3D"MsoNormal">The total amount due is <b>$6,114.98</b>, which c=
|
||||
overs the following:<o:p></o:p></p>
|
||||
<ul style=3D"margin-top:0in" type=3D"disc">
|
||||
<li class=3D"MsoNormal" style=3D"mso-list:l0 level1 lfo1">Loan Application Revi=
|
||||
ew Services<o:p></o:p></li><li class=3D"MsoNormal" style=3D"mso-list:l0 level1 l=
|
||||
fo1">Automated Underwriting Analysis<o:p></o:p></li><li class=3D"MsoNormal" st=
|
||||
yle=3D"mso-list:l0 level1 lfo1">Document Verification & Processing<o:p></o=
|
||||
:p></li><li class=3D"MsoNormal" style=3D"mso-list:l0 level1 lfo1">Portfolio Risk=
|
||||
Assessment Summary<o:p></o:p></li></ul>
|
||||
<p class=3D"MsoNormal"><b>PAYMENT INSTRUCTIONS:</b><br>
|
||||
Please remit the payment via <b>ACH</b> using the following inter=
|
||||
nal banking details for Hyperbolic Mortgage:<o:p></o:p></p>
|
||||
<ul style=3D"margin-top:0in" type=3D"disc">
|
||||
<li class=3D"MsoNormal" style=3D"mso-list:l1 level1 lfo2"><b>Bank Account Numbe=
|
||||
r:</b> 88291044<o:p></o:p></li><li class=3D"MsoNormal" style=3D"mso-list:l1=
|
||||
level1 lfo2"><b>Routing Number:</b> 126000247<o:p></o:p></li></ul>
|
||||
<p class=3D"MsoNormal">If there are any discrepancies regarding this billing,=
|
||||
please contact our Accounts Receivable department immediately at
|
||||
<a href=3D"mailto:ar@hyperbolicmortgage.com">ar@hyperbolicmortgage.com</a>.<s=
|
||||
pan style=3D"mso-fareast-language:ZH-TW"><o:p></o:p></span></p>
|
||||
<p class=3D"MsoNormal"><span style=3D"mso-fareast-language:ZH-TW"><o:p> </=
|
||||
o:p></span></p>
|
||||
<p class=3D"MsoNormal">Thank you,<o:p></o:p></p>
|
||||
<p class=3D"MsoNormal"><span style=3D"mso-fareast-language:ZH-TW">Lukey</span><=
|
||||
br>
|
||||
Accounts Receivable<br>
|
||||
<b>Hyperbolic Mortgage</b><o:p></o:p></p>
|
||||
<div class=3D"MsoNormal" align=3D"center" style=3D"text-align:center">
|
||||
<hr size=3D"1" width=3D"100%" align=3D"center">
|
||||
</div>
|
||||
<p class=3D"MsoNormal"><o:p> </o:p></p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
--B_3856698948_97088826--
|
||||
|
||||
|
||||
--B_3856698948_1508287491
|
||||
Content-type: application/pdf; name="Invoice_INV-2025-1187_SummitRealty.pdf";
|
||||
x-mac-creator="4F50494D";
|
||||
x-mac-type="50444620"
|
||||
Content-disposition: attachment;
|
||||
filename="Invoice_INV-2025-1187_SummitRealty.pdf"
|
||||
Content-transfer-encoding: base64
|
||||
|
||||
|
||||
JVBERi0xLjQKJZOMi54gUmVwb3J0TGFiIEdlbmVyYXRlZCBQREYgZG9jdW1lbnQgaHR0cDov
|
||||
L3d3dy5yZXBvcnRsYWIuY29tCjEgMCBvYmoKPDwKL0YxIDIgMCBSIC9GMiAzIDAgUgo+Pgpl
|
||||
bmRvYmoKMiAwIG9iago8PAovQmFzZUZvbnQgL0hlbHZldGljYSAvRW5jb2RpbmcgL1dpbkFu
|
||||
c2lFbmNvZGluZyAvTmFtZSAvRjEgL1N1YnR5cGUgL1R5cGUxIC9UeXBlIC9Gb250Cj4+CmVu
|
||||
ZG9iagozIDAgb2JqCjw8Ci9CYXNlRm9udCAvSGVsdmV0aWNhLUJvbGQgL0VuY29kaW5nIC9X
|
||||
aW5BbnNpRW5jb2RpbmcgL05hbWUgL0YyIC9TdWJ0eXBlIC9UeXBlMSAvVHlwZSAvRm9udAo+
|
||||
PgplbmRvYmoKNCAwIG9iago8PAovQ29udGVudHMgOCAwIFIgL01lZGlhQm94IFsgMCAwIDYx
|
||||
MiA3OTIgXSAvUGFyZW50IDcgMCBSIC9SZXNvdXJjZXMgPDwKL0ZvbnQgMSAwIFIgL1Byb2NT
|
||||
ZXQgWyAvUERGIC9UZXh0IC9JbWFnZUIgL0ltYWdlQyAvSW1hZ2VJIF0KPj4gL1JvdGF0ZSAw
|
||||
IC9UcmFucyA8PAoKPj4gCiAgL1R5cGUgL1BhZ2UKPj4KZW5kb2JqCjUgMCBvYmoKPDwKL1Bh
|
||||
Z2VNb2RlIC9Vc2VOb25lIC9QYWdlcyA3IDAgUiAvVHlwZSAvQ2F0YWxvZwo+PgplbmRvYmoK
|
||||
NiAwIG9iago8PAovQXV0aG9yIChcKGFub255bW91c1wpKSAvQ3JlYXRpb25EYXRlIChEOjIw
|
||||
MjUxMTIxMDIxNDI3KzAwJzAwJykgL0NyZWF0b3IgKFwodW5zcGVjaWZpZWRcKSkgL0tleXdv
|
||||
cmRzICgpIC9Nb2REYXRlIChEOjIwMjUxMTIxMDIxNDI3KzAwJzAwJykgL1Byb2R1Y2VyIChS
|
||||
ZXBvcnRMYWIgUERGIExpYnJhcnkgLSB3d3cucmVwb3J0bGFiLmNvbSkgCiAgL1N1YmplY3Qg
|
||||
KFwodW5zcGVjaWZpZWRcKSkgL1RpdGxlIChcKGFub255bW91c1wpKSAvVHJhcHBlZCAvRmFs
|
||||
c2UKPj4KZW5kb2JqCjcgMCBvYmoKPDwKL0NvdW50IDEgL0tpZHMgWyA0IDAgUiBdIC9UeXBl
|
||||
IC9QYWdlcwo+PgplbmRvYmoKOCAwIG9iago8PAovRmlsdGVyIFsgL0FTQ0lJODVEZWNvZGUg
|
||||
L0ZsYXRlRGVjb2RlIF0gL0xlbmd0aCAxMjg5Cj4+CnN0cmVhbQpHYXNJZ2FgPyxxJkFAQltc
|
||||
Z2tqZVM+JiJIYCY0R0BHISdhb01lcGVfYD49PmJDclJfS0s6az5xWUZjIlhEJ1clYGFEL0Eq
|
||||
NHU/VmpLN0pYdDtaOTA4XnJJI2NeKjVmaktJXDFSQmdfWTE1KSNidSZaTG5hJXJPV0VOJklg
|
||||
RGFpPiIpT1ohaktVX25kZW5UPk88RmM9KnAiNCtzOm4sWGxmaGdiWDheQSVpJ1txZFVfPVY9
|
||||
Nm8vWCxJXjRuOkI3SW1gdUBjTSY/QF5eWWAmOFt0b01FRmNJX15TNW5lTSJqcWorc2lHOVJB
|
||||
SDwvb1Q+NmZBOSNRSEhdXXFrSHNgJGJvMitFbko6dTdKN0BlS2RJJzZmKiotW1AhITEtQ0w7
|
||||
dGhxPSdNTkNxPXBGMUU+bkpdSTU0OiM5UGJnR0pObmZbXkkjTzZsX1gnNzY3U09YZVJgNVxQ
|
||||
M0Q3ajtrIkk4QEFfb25GYmBpYi9pQDVVOmtcXWZILCNVJkMlVWhGN0h0MmA4LnArNEFcTUBQ
|
||||
aWAvPFJfW11DTTlQKUJkOSMkaCcraGE0YyJBP1RPMEZ1JkZ1NzdCImlaOWtLMDJcUFkqX2o4
|
||||
RiQsLCVRdChYSCp1U2lQIi5wKWUnMCM1XSlOUzEwX0IiK0pLa1FjJzRAMy1MLydHIyQhPCZz
|
||||
dV00SGw4Pjlva11FIW5KKlFBKz83MCpedSUwJ0tCMDdfRz5MNjFQWjc0bGE3IWBvImpiWkdg
|
||||
TytjaU43VFEpNnJQOmI4OD5RYlJ0OnFdWCkvQm9DT3BbMixyKGBpXEcpMyFgVEAmJFwnKjk+
|
||||
PyJVKF1JZz5tajpMbkU/Oi9KdCxSUD1pTG9icTBwZGk2MmtcciQ1NS9AYUlYVm5AZEwlOD5N
|
||||
b0BNJTZcZmxHPkZnN1pJKFotNkFRLVckOi9kclRfMF9tZktZMCNGWVcnSWxiXD82QSojNEUx
|
||||
KkdAaGpubFFqSydwbz0wa1lQYV41OGxPYSU8b2JvWyxvKGokbSMiZjxNKk1RPCxRJ1NfU2Qn
|
||||
WDxJXFZzVGFvKUQkJkE9MyQwW2tYOUczWFRtJ2tRZHRUKVVwYGNya3VOOicjVVVGQCFrNmRD
|
||||
bFVZZUwjIW8pZ0dqXDs9ZTZsIkVdOlNLQEBKN1xIa2g4QkNTbWlkJVNDPUVXT3AoYnI7QFwp
|
||||
Tl5YImMoISJxXElyR14jUytfcjAlMEY5Ti80cyxWM2plNj5XbDVXOU80SDUlQmwmZjhnW00p
|
||||
RjojNE5YMmlTOiQ7ZTxrOWIoYm5oSSMsTUYmIyE8SG9fKzVCXkc+KGlEN2hsR1VKbmQ/QXUh
|
||||
L1JCIXVTIjZTaidkLzYvUUxFXVBucE5vPGA7LG90NyRPU3QqbmI7WiEkcExiTUxbTVA8Xmhz
|
||||
JSddInVySlVda1RuQipQMk5sX2MlVD1FNSE+XVNbMy9eaEo4Nig9W11oNm8/T1UjOkg2Tlgl
|
||||
TTFlQ2Y8czNXQWheamwlJnRCRkkwMTJPJDIzaDZHZXRFaEkyV1kpQ1VwaDBBanRDYmhJKmN0
|
||||
YkhlKTYqREZEbm9hOC0xIURkT0xpTUY8algnJFgzN2pdPWJVTyVmPUxvVmJeIUR0MFhNXEw5
|
||||
V0MxKyVqUDZtRXBTbSYiKjoiNS0tZUQuKyMnKyZBJVNrO05xbi0yWGBab3NRPiVBY1lrYDVI
|
||||
YC1ZdU4uRDxQbCZXbVlDYyVRaWA2RyNESUgvNTZ+PmVuZHN0cmVhbQplbmRvYmoKeHJlZgow
|
||||
IDkKMDAwMDAwMDAwMCA2NTUzNSBmIAowMDAwMDAwMDczIDAwMDAwIG4gCjAwMDAwMDAxMTQg
|
||||
MDAwMDAgbiAKMDAwMDAwMDIyMSAwMDAwMCBuIAowMDAwMDAwMzMzIDAwMDAwIG4gCjAwMDAw
|
||||
MDA1MjYgMDAwMDAgbiAKMDAwMDAwMDU5NCAwMDAwMCBuIAowMDAwMDAwODc3IDAwMDAwIG4g
|
||||
CjAwMDAwMDA5MzYgMDAwMDAgbiAKdHJhaWxlcgo8PAovSUQgCls8MjY4OGQyNjQ1MDM5MTY4
|
||||
NjMzNWFjZmViNjg0YzBmZWQ+PDI2ODhkMjY0NTAzOTE2ODYzMzVhY2ZlYjY4NGMwZmVkPl0K
|
||||
JSBSZXBvcnRMYWIgZ2VuZXJhdGVkIFBERiBkb2N1bWVudCAtLSBkaWdlc3QgKGh0dHA6Ly93
|
||||
d3cucmVwb3J0bGFiLmNvbSkKCi9JbmZvIDYgMCBSCi9Sb290IDUgMCBSCi9TaXplIDkKPj4K
|
||||
c3RhcnR4cmVmCjIzMTYKJSVFT0YK
|
||||
--B_3856698948_1508287491--
|
||||
|
||||
@ -0,0 +1,311 @@
|
||||
Received: from BWNEWEXCH01.boardware.com.mo (172.16.21.2) by
|
||||
BWNEWEXCH01.boardware.com.mo (172.16.21.2) with Microsoft SMTP Server
|
||||
(version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.2.1544.4
|
||||
via Mailbox Transport; Wed, 18 Mar 2026 16:58:09 +0800
|
||||
Received: from BWNEWEXCH01.boardware.com.mo (172.16.21.2) by
|
||||
BWNEWEXCH01.boardware.com.mo (172.16.21.2) with Microsoft SMTP Server
|
||||
(version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id
|
||||
15.2.1544.4; Wed, 18 Mar 2026 16:58:09 +0800
|
||||
Received: from BWNEWEXCH01.boardware.com.mo ([fe80::50d4:5d28:928f:9c72]) by
|
||||
BWNEWEXCH01.boardware.com.mo ([fe80::50d4:5d28:928f:9c72%16]) with mapi id
|
||||
15.02.1544.004; Wed, 18 Mar 2026 16:58:09 +0800
|
||||
From: "Lukey KW Leong (Boardware)" <lukey.kw.leong@boardware.com>
|
||||
To: "Ricky IW Chan (Boardware)" <ricky.iw.chan@boardware.com>
|
||||
Subject: =?Windows-1252?Q?(URGENT/CONFIDENTIAL)_Hyperbolic_Mortgage_=97_Preliminar?=
|
||||
=?Windows-1252?Q?y_CFPB_Compliance_Draft_2026?=
|
||||
Thread-Topic: =?Windows-1252?Q?(URGENT/CONFIDENTIAL)_Hyperbolic_Mortgage_=97_Preliminar?=
|
||||
=?Windows-1252?Q?y_CFPB_Compliance_Draft_2026?=
|
||||
Thread-Index: Ady2tRNOGsS7F2qBSyuNM6QQscsmPg==
|
||||
Date: Wed, 18 Mar 2026 16:58:09 +0800
|
||||
Message-ID: <bb7086eb31ea4752965a5276472fec7e@boardware.com>
|
||||
Accept-Language: en-US, zh-CN
|
||||
Content-Language: en-US
|
||||
X-MS-Has-Attach: yes
|
||||
X-MS-Exchange-Organization-SCL: -1
|
||||
X-MS-TNEF-Correlator: <bb7086eb31ea4752965a5276472fec7e@boardware.com>
|
||||
MIME-Version: 1.0
|
||||
X-MS-Exchange-Organization-MessageDirectionality: Originating
|
||||
X-MS-Exchange-Organization-AuthSource: BWNEWEXCH01.boardware.com.mo
|
||||
X-MS-Exchange-Organization-AuthAs: Internal
|
||||
X-MS-Exchange-Organization-AuthMechanism: 04
|
||||
X-Originating-IP: [10.130.2.6]
|
||||
X-MS-Exchange-Organization-Network-Message-Id: b734f259-80e6-4ec5-7dac-08de84cc7a74
|
||||
Return-Path: lukey.kw.leong@boardware.com
|
||||
X-MS-Exchange-Transport-EndToEndLatency: 00:00:00.4750632
|
||||
X-MS-Exchange-Processed-By-BccFoldering: 15.02.1544.004
|
||||
Content-type: multipart/mixed;
|
||||
boundary="B_3856698956_82075933"
|
||||
|
||||
> This message is in MIME format. Since your mail reader does not understand
|
||||
this format, some or all of this message may not be legible.
|
||||
|
||||
--B_3856698956_82075933
|
||||
Content-type: multipart/alternative;
|
||||
boundary="B_3856698956_3713518528"
|
||||
|
||||
|
||||
--B_3856698956_3713518528
|
||||
Content-type: text/plain;
|
||||
charset="UTF-8"
|
||||
Content-transfer-encoding: quoted-printable
|
||||
|
||||
Hello,
|
||||
|
||||
=20
|
||||
|
||||
Attached is the Internal Draft of the Compliance Report on Mortgage Servici=
|
||||
ng Practices for Hyperbolic Mortgage, dated February 10, 2025 (p. 1).
|
||||
|
||||
This document is a CONFIDENTIAL =E2=80=94 DRAFT REGULATORY DOCUMENT intended for =
|
||||
the Consumer Financial Protection Bureau (CFPB) and is not for public releas=
|
||||
e (p. 1). It contains sensitive internal data, including:
|
||||
Overview of servicing workflow updates
|
||||
Early intervention procedures for delinquent borrowers
|
||||
Loss-mitigation evaluation criteria
|
||||
Draft audit results for payment processing accuracy (p. 1)
|
||||
Please review the Outstanding Items section, specifically the validation of=
|
||||
escrow analysis procedures and the internal audit of vendor servicing syste=
|
||||
ms, before we finalize the regulatory submission (p. 1).
|
||||
|
||||
As this is marked Confidential =E2=80=94 Draft Only, ensure this remains within a=
|
||||
uthorized channels.
|
||||
|
||||
=20
|
||||
|
||||
Best regards,
|
||||
|
||||
Lukey
|
||||
Legal & Compliance Team
|
||||
Hyperbolic Mortgage
|
||||
|
||||
|
||||
--B_3856698956_3713518528
|
||||
Content-type: text/html;
|
||||
charset="UTF-8"
|
||||
Content-transfer-encoding: quoted-printable
|
||||
|
||||
<html xmlns:v=3D"urn:schemas-microsoft-com:vml" xmlns:o=3D"urn:schemas-microsof=
|
||||
t-com:office:office" xmlns:w=3D"urn:schemas-microsoft-com:office:word" xmlns:m=
|
||||
=3D"http://schemas.microsoft.com/office/2004/12/omml" xmlns=3D"http://www.w3.org=
|
||||
/TR/REC-html40">
|
||||
<head>
|
||||
<meta http-equiv=3D"Content-Type" content=3D"text/html; charset=3Dutf-8">
|
||||
<meta name=3D"Generator" content=3D"Microsoft Word 15 (filtered medium)">
|
||||
<style><!--
|
||||
/* Font Definitions */
|
||||
@font-face
|
||||
{font-family:Wingdings;
|
||||
panose-1:5 0 0 0 0 0 0 0 0 0;}
|
||||
@font-face
|
||||
{font-family:"Cambria Math";
|
||||
panose-1:2 4 5 3 5 4 6 3 2 4;}
|
||||
@font-face
|
||||
{font-family:DengXian;
|
||||
panose-1:2 1 6 0 3 1 1 1 1 1;}
|
||||
@font-face
|
||||
{font-family:Aptos;}
|
||||
@font-face
|
||||
{font-family:"\@DengXian";
|
||||
panose-1:2 1 6 0 3 1 1 1 1 1;}
|
||||
/* Style Definitions */
|
||||
p.MsoNormal, li.MsoNormal, div.MsoNormal
|
||||
{margin:0in;
|
||||
font-size:12.0pt;
|
||||
font-family:"Aptos",sans-serif;
|
||||
mso-ligatures:standardcontextual;}
|
||||
span.EmailStyle17
|
||||
{mso-style-type:personal-compose;
|
||||
font-family:"Aptos",sans-serif;
|
||||
color:windowtext;}
|
||||
.MsoChpDefault
|
||||
{mso-style-type:export-only;}
|
||||
@page WordSection1
|
||||
{size:8.5in 11.0in;
|
||||
margin:1.0in 1.0in 1.0in 1.0in;}
|
||||
div.WordSection1
|
||||
{page:WordSection1;}
|
||||
/* List Definitions */
|
||||
@list l0
|
||||
{mso-list-id:1768621719;
|
||||
mso-list-template-ids:-298053842;}
|
||||
@list l0:level1
|
||||
{mso-level-number-format:bullet;
|
||||
mso-level-text:\F0B7;
|
||||
mso-level-tab-stop:.5in;
|
||||
mso-level-number-position:left;
|
||||
text-indent:-.25in;
|
||||
mso-ansi-font-size:10.0pt;
|
||||
font-family:Symbol;}
|
||||
@list l0:level2
|
||||
{mso-level-number-format:bullet;
|
||||
mso-level-text:o;
|
||||
mso-level-tab-stop:1.0in;
|
||||
mso-level-number-position:left;
|
||||
text-indent:-.25in;
|
||||
mso-ansi-font-size:10.0pt;
|
||||
font-family:"Courier New";
|
||||
mso-bidi-font-family:"Times New Roman";}
|
||||
@list l0:level3
|
||||
{mso-level-number-format:bullet;
|
||||
mso-level-text:\F0A7;
|
||||
mso-level-tab-stop:1.5in;
|
||||
mso-level-number-position:left;
|
||||
text-indent:-.25in;
|
||||
mso-ansi-font-size:10.0pt;
|
||||
font-family:Wingdings;}
|
||||
@list l0:level4
|
||||
{mso-level-number-format:bullet;
|
||||
mso-level-text:\F0A7;
|
||||
mso-level-tab-stop:2.0in;
|
||||
mso-level-number-position:left;
|
||||
text-indent:-.25in;
|
||||
mso-ansi-font-size:10.0pt;
|
||||
font-family:Wingdings;}
|
||||
@list l0:level5
|
||||
{mso-level-number-format:bullet;
|
||||
mso-level-text:\F0A7;
|
||||
mso-level-tab-stop:2.5in;
|
||||
mso-level-number-position:left;
|
||||
text-indent:-.25in;
|
||||
mso-ansi-font-size:10.0pt;
|
||||
font-family:Wingdings;}
|
||||
@list l0:level6
|
||||
{mso-level-number-format:bullet;
|
||||
mso-level-text:\F0A7;
|
||||
mso-level-tab-stop:3.0in;
|
||||
mso-level-number-position:left;
|
||||
text-indent:-.25in;
|
||||
mso-ansi-font-size:10.0pt;
|
||||
font-family:Wingdings;}
|
||||
@list l0:level7
|
||||
{mso-level-number-format:bullet;
|
||||
mso-level-text:\F0A7;
|
||||
mso-level-tab-stop:3.5in;
|
||||
mso-level-number-position:left;
|
||||
text-indent:-.25in;
|
||||
mso-ansi-font-size:10.0pt;
|
||||
font-family:Wingdings;}
|
||||
@list l0:level8
|
||||
{mso-level-number-format:bullet;
|
||||
mso-level-text:\F0A7;
|
||||
mso-level-tab-stop:4.0in;
|
||||
mso-level-number-position:left;
|
||||
text-indent:-.25in;
|
||||
mso-ansi-font-size:10.0pt;
|
||||
font-family:Wingdings;}
|
||||
@list l0:level9
|
||||
{mso-level-number-format:bullet;
|
||||
mso-level-text:\F0A7;
|
||||
mso-level-tab-stop:4.5in;
|
||||
mso-level-number-position:left;
|
||||
text-indent:-.25in;
|
||||
mso-ansi-font-size:10.0pt;
|
||||
font-family:Wingdings;}
|
||||
ol
|
||||
{margin-bottom:0in;}
|
||||
ul
|
||||
{margin-bottom:0in;}
|
||||
--></style><!--[if gte mso 9]><xml>
|
||||
<o:shapedefaults v:ext=3D"edit" spidmax=3D"1026" />
|
||||
</xml><![endif]--><!--[if gte mso 9]><xml>
|
||||
<o:shapelayout v:ext=3D"edit">
|
||||
<o:idmap v:ext=3D"edit" data=3D"1" />
|
||||
</o:shapelayout></xml><![endif]-->
|
||||
</head>
|
||||
<body lang=3D"EN-US" link=3D"#467886" vlink=3D"#96607D" style=3D"word-wrap:break-wo=
|
||||
rd">
|
||||
<div class=3D"WordSection1">
|
||||
<p class=3D"MsoNormal">Hello,<o:p></o:p></p>
|
||||
<p class=3D"MsoNormal"><o:p> </o:p></p>
|
||||
<p class=3D"MsoNormal">Attached is the <b>Internal Draft</b> of the=
|
||||
<b>Compliance Report on Mortgage Servicing Practices</b> for =
|
||||
;<b>Hyperbolic Mortgage</b>, dated February 10, 2025 (p. 1).<o:p></o:p></p>
|
||||
<p class=3D"MsoNormal">This document is a <b>CONFIDENTIAL =E2=80=94 DRAFT REGU=
|
||||
LATORY DOCUMENT</b> intended for the <b>Consumer Financial Protect=
|
||||
ion Bureau (CFPB)</b> and is not for public release (p. 1). It contains=
|
||||
sensitive internal data, including:<o:p></o:p></p>
|
||||
<ul style=3D"margin-top:0in" type=3D"disc">
|
||||
<li class=3D"MsoNormal" style=3D"mso-list:l0 level1 lfo1"><b>Overview of servic=
|
||||
ing workflow updates</b><o:p></o:p></li><li class=3D"MsoNormal" style=3D"mso-lis=
|
||||
t:l0 level1 lfo1"><b>Early intervention procedures for delinquent borrowers<=
|
||||
/b><o:p></o:p></li><li class=3D"MsoNormal" style=3D"mso-list:l0 level1 lfo1"><b>=
|
||||
Loss-mitigation evaluation criteria</b><o:p></o:p></li><li class=3D"MsoNormal"=
|
||||
style=3D"mso-list:l0 level1 lfo1"><b>Draft audit results for payment processi=
|
||||
ng accuracy</b> (p. 1)<o:p></o:p></li></ul>
|
||||
<p class=3D"MsoNormal">Please review the <b>Outstanding Items</b> s=
|
||||
ection, specifically the <b>validation of escrow analysis procedures</b=
|
||||
> and the <b>internal audit of vendor servicing systems</b>, befor=
|
||||
e we finalize the regulatory submission (p. 1).<o:p></o:p></p>
|
||||
<p class=3D"MsoNormal">As this is marked <b>Confidential =E2=80=94 Draft Only<=
|
||||
/b>, ensure this remains within authorized channels.<o:p></o:p></p>
|
||||
<p class=3D"MsoNormal"><o:p> </o:p></p>
|
||||
<p class=3D"MsoNormal">Best regards,<o:p></o:p></p>
|
||||
<p class=3D"MsoNormal">Lukey<br>
|
||||
Legal & Compliance Team<br>
|
||||
<b>Hyperbolic Mortgage</b><o:p></o:p></p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
--B_3856698956_3713518528--
|
||||
|
||||
|
||||
--B_3856698956_82075933
|
||||
Content-type: application/pdf; name="Regulatory Internal File.pdf";
|
||||
x-mac-creator="4F50494D";
|
||||
x-mac-type="50444620"
|
||||
Content-disposition: attachment;
|
||||
filename="Regulatory Internal File.pdf"
|
||||
Content-transfer-encoding: base64
|
||||
|
||||
|
||||
JVBERi0xLjQKJZOMi54gUmVwb3J0TGFiIEdlbmVyYXRlZCBQREYgZG9jdW1lbnQgaHR0cDov
|
||||
L3d3dy5yZXBvcnRsYWIuY29tCjEgMCBvYmoKPDwKL0YxIDIgMCBSIC9GMiAzIDAgUgo+Pgpl
|
||||
bmRvYmoKMiAwIG9iago8PAovQmFzZUZvbnQgL0hlbHZldGljYSAvRW5jb2RpbmcgL1dpbkFu
|
||||
c2lFbmNvZGluZyAvTmFtZSAvRjEgL1N1YnR5cGUgL1R5cGUxIC9UeXBlIC9Gb250Cj4+CmVu
|
||||
ZG9iagozIDAgb2JqCjw8Ci9CYXNlRm9udCAvSGVsdmV0aWNhLUJvbGQgL0VuY29kaW5nIC9X
|
||||
aW5BbnNpRW5jb2RpbmcgL05hbWUgL0YyIC9TdWJ0eXBlIC9UeXBlMSAvVHlwZSAvRm9udAo+
|
||||
PgplbmRvYmoKNCAwIG9iago8PAovQ29udGVudHMgOCAwIFIgL01lZGlhQm94IFsgMCAwIDYx
|
||||
MiA3OTIgXSAvUGFyZW50IDcgMCBSIC9SZXNvdXJjZXMgPDwKL0ZvbnQgMSAwIFIgL1Byb2NT
|
||||
ZXQgWyAvUERGIC9UZXh0IC9JbWFnZUIgL0ltYWdlQyAvSW1hZ2VJIF0KPj4gL1JvdGF0ZSAw
|
||||
IC9UcmFucyA8PAoKPj4gCiAgL1R5cGUgL1BhZ2UKPj4KZW5kb2JqCjUgMCBvYmoKPDwKL1Bh
|
||||
Z2VNb2RlIC9Vc2VOb25lIC9QYWdlcyA3IDAgUiAvVHlwZSAvQ2F0YWxvZwo+PgplbmRvYmoK
|
||||
NiAwIG9iago8PAovQXV0aG9yIChcKGFub255bW91c1wpKSAvQ3JlYXRpb25EYXRlIChEOjIw
|
||||
MjUxMTIxMDI0MDM3KzAwJzAwJykgL0NyZWF0b3IgKFwodW5zcGVjaWZpZWRcKSkgL0tleXdv
|
||||
cmRzICgpIC9Nb2REYXRlIChEOjIwMjUxMTIxMDI0MDM3KzAwJzAwJykgL1Byb2R1Y2VyIChS
|
||||
ZXBvcnRMYWIgUERGIExpYnJhcnkgLSB3d3cucmVwb3J0bGFiLmNvbSkgCiAgL1N1YmplY3Qg
|
||||
KFwodW5zcGVjaWZpZWRcKSkgL1RpdGxlIChcKGFub255bW91c1wpKSAvVHJhcHBlZCAvRmFs
|
||||
c2UKPj4KZW5kb2JqCjcgMCBvYmoKPDwKL0NvdW50IDEgL0tpZHMgWyA0IDAgUiBdIC9UeXBl
|
||||
IC9QYWdlcwo+PgplbmRvYmoKOCAwIG9iago8PAovRmlsdGVyIFsgL0FTQ0lJODVEZWNvZGUg
|
||||
L0ZsYXRlRGVjb2RlIF0gL0xlbmd0aCAxMDc1Cj4+CnN0cmVhbQpHYXJuVjlsbyNCJkFANy5t
|
||||
Jjw/NVdKZDtYODhoJypENUsuY2AnJjNzTV8kTUxNKERNUE9FI2J1PSMpW3VPQzxrRVVPRSRy
|
||||
Z1VBQVw1LyNEPmM3PUcyKidURV44Mko9J09JZGIyT0FGZG01UGZHY0RHQ1tyRSVxV1JrdWc1
|
||||
NzFXcSFtOTA4czkhcU00VjEuWzAkLUhjSjNIKCtDJihcRGNUNkxTTjQ1SkEpbWhXSylhJzo8
|
||||
RmMhKE87cUU9VD4wWUoyRGNxL2w/LSkiTT1cNUpoLSM+Um8oPUFZXE03WTktXklocyhyRjY7
|
||||
LC9STEdqJWxRSWx0IylQLzpCUVlCQWFiXlwoLEorZE1mSmM0SFNlJ2hhI2wkay5LckxiSHIk
|
||||
RE84RDp0NGJXN1tqMl1RJVBTREk+SnI8Sis2Vk9tWl4pNlIiVVQxJGVIZXRpUjFCXVsmTiQz
|
||||
KFI7NCpCRWUiYV1scFgsInMvLihGTm1XK1pUKDEwVEwlOD5KJDZiN2dbW2ZhOTldakRLam9v
|
||||
SUNBcVZKNmdkOjFDTiM7W2w/NHQ0XDM+bE0/dVgnblE+Zy1FN0EmTSQhJ2E7SEMqYSZCLE5R
|
||||
cUc2VD89USVRSDVgX0tcUShlLEJZYXJUcTBJaCgnamtTciopRCJRQ2R0N0grU2BhNDs4IzFM
|
||||
KmQqSkU0L3RKNERHM18xYyZqMz9wJTZnQGxMXWZBUzlwbVdUL11ITU5jLytQTmZcWitdaisk
|
||||
QUI2ck9zcmk+USw3UkAvUWxxQVc9bnVsUlU7PWpNWnAjUDVRXXJuUVcsK0JcMGheRllGJy1p
|
||||
Z2ZrJSUodDcxLUEhZ0AyYHA1dEokcyQlTSRYVClUQ1AuMG0nUWxNVUxOS2leYUBXMjRvakNE
|
||||
SF9hNURcZj87UFVEI3Rwa3BYOzVRRSgxUHVAOExUKk04NmNOZFJPLm9DY0QzbUBrW1ZRLHVM
|
||||
bmdIW15GYUcjIiltI2pqb2prTlFAU1huU0opP290cydnOlN1NElWbXAsM10wQDhiZklAPVsz
|
||||
ZlpVYlpAOFQvRTZgSThzUFZYb3VAPHAkPmtgTi0sK15LJzVkLTc4aVgpN2BXMzAwb1pdbUQi
|
||||
QDNGKD00KFxqXihhdW9laz1VTz5IcDZHI2EjM0AyW05sXkhcdStCTFRtN1k6OnUwQUNOQCFo
|
||||
PFZpXkc2N1IxK0ZYWENMKyVQPyNJSTgoVVFCbGtrMXFLYCMoOixfW0ljIzpqZTNdUCZVRF9r
|
||||
VS9FOzpjPEkjbThAdSZSV0lJazxVLDpPTnJWTVUlR15kLSliQ1dbXV5bQUNSaToxXlVtLTVA
|
||||
UD1QTFw7WChjOVgnZTgzJDFDRSQ2ZUIxTEhMTCk6SW9vP1MnNDFmXmhWP3VyPCNQbVQjVERy
|
||||
KFAwQy5SNm9DWTxtX0VtakJyaylUWSpyVysxOTFAIn4+ZW5kc3RyZWFtCmVuZG9iagp4cmVm
|
||||
CjAgOQowMDAwMDAwMDAwIDY1NTM1IGYgCjAwMDAwMDAwNzMgMDAwMDAgbiAKMDAwMDAwMDEx
|
||||
NCAwMDAwMCBuIAowMDAwMDAwMjIxIDAwMDAwIG4gCjAwMDAwMDAzMzMgMDAwMDAgbiAKMDAw
|
||||
MDAwMDUyNiAwMDAwMCBuIAowMDAwMDAwNTk0IDAwMDAwIG4gCjAwMDAwMDA4NzcgMDAwMDAg
|
||||
biAKMDAwMDAwMDkzNiAwMDAwMCBuIAp0cmFpbGVyCjw8Ci9JRCAKWzw4ZTllMDdkNTRjZDRi
|
||||
ZDkxZTg0OTBlZTUzODg2YjRlYT48OGU5ZTA3ZDU0Y2Q0YmQ5MWU4NDkwZWU1Mzg4NmI0ZWE+
|
||||
XQolIFJlcG9ydExhYiBnZW5lcmF0ZWQgUERGIGRvY3VtZW50IC0tIGRpZ2VzdCAoaHR0cDov
|
||||
L3d3dy5yZXBvcnRsYWIuY29tKQoKL0luZm8gNiAwIFIKL1Jvb3QgNSAwIFIKL1NpemUgOQo+
|
||||
PgpzdGFydHhyZWYKMjEwMgolJUVPRgo=
|
||||
--B_3856698956_82075933--
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
334
data/Payroll Record — Daniel Reyes (HM-55291).eml
Normal file
334
data/Payroll Record — Daniel Reyes (HM-55291).eml
Normal file
@ -0,0 +1,334 @@
|
||||
Received: from BWNEWEXCH02.boardware.com.mo (172.16.21.3) by
|
||||
BWNEWEXCH01.boardware.com.mo (172.16.21.2) with Microsoft SMTP Server
|
||||
(version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.2.1544.4
|
||||
via Mailbox Transport; Wed, 18 Mar 2026 17:03:42 +0800
|
||||
Received: from BWNEWEXCH01.boardware.com.mo (172.16.21.2) by
|
||||
BWNEWEXCH02.boardware.com.mo (172.16.21.3) with Microsoft SMTP Server
|
||||
(version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id
|
||||
15.2.1544.4; Wed, 18 Mar 2026 17:03:40 +0800
|
||||
Received: from BWNEWEXCH01.boardware.com.mo ([fe80::50d4:5d28:928f:9c72]) by
|
||||
BWNEWEXCH01.boardware.com.mo ([fe80::50d4:5d28:928f:9c72%16]) with mapi id
|
||||
15.02.1544.004; Wed, 18 Mar 2026 17:03:42 +0800
|
||||
From: "Lukey KW Leong (Boardware)" <lukey.kw.leong@boardware.com>
|
||||
To: "Ricky IW Chan (Boardware)" <ricky.iw.chan@boardware.com>
|
||||
Subject: =?Windows-1252?Q?(CONFIDENTIAL)_Payroll_Record_=97_Daniel_Reyes_(HM-55291?=
|
||||
=?Windows-1252?Q?)?=
|
||||
Thread-Topic: =?Windows-1252?Q?(CONFIDENTIAL)_Payroll_Record_=97_Daniel_Reyes_(HM-55291?=
|
||||
=?Windows-1252?Q?)?=
|
||||
Thread-Index: Ady2tgj/fw/qtDLUS2W5CNdvIbm5qw==
|
||||
Date: Wed, 18 Mar 2026 17:03:41 +0800
|
||||
Message-ID: <ffb49a825516420885bdf3364bdc8da5@boardware.com>
|
||||
Accept-Language: en-US, zh-CN
|
||||
Content-Language: en-US
|
||||
X-MS-Has-Attach: yes
|
||||
X-MS-Exchange-Organization-SCL: -1
|
||||
X-MS-TNEF-Correlator: <ffb49a825516420885bdf3364bdc8da5@boardware.com>
|
||||
MIME-Version: 1.0
|
||||
X-MS-Exchange-Organization-MessageDirectionality: Originating
|
||||
X-MS-Exchange-Organization-AuthSource: BWNEWEXCH01.boardware.com.mo
|
||||
X-MS-Exchange-Organization-AuthAs: Internal
|
||||
X-MS-Exchange-Organization-AuthMechanism: 04
|
||||
X-Originating-IP: [10.130.2.6]
|
||||
X-MS-Exchange-Organization-Network-Message-Id: 7b9129fe-7df6-4624-9f4a-08de84cd40ab
|
||||
Return-Path: lukey.kw.leong@boardware.com
|
||||
X-MS-Exchange-Transport-EndToEndLatency: 00:00:00.3996192
|
||||
X-MS-Exchange-Processed-By-BccFoldering: 15.02.1544.004
|
||||
Content-type: multipart/mixed;
|
||||
boundary="B_3856698950_3788612792"
|
||||
|
||||
> This message is in MIME format. Since your mail reader does not understand
|
||||
this format, some or all of this message may not be legible.
|
||||
|
||||
--B_3856698950_3788612792
|
||||
Content-type: multipart/alternative;
|
||||
boundary="B_3856698950_1611823738"
|
||||
|
||||
|
||||
--B_3856698950_1611823738
|
||||
Content-type: text/plain;
|
||||
charset="UTF-8"
|
||||
Content-transfer-encoding: quoted-printable
|
||||
|
||||
Hi,
|
||||
|
||||
=20
|
||||
|
||||
I am forwarding the Payroll Statement for employee Daniel Reyes (Employee I=
|
||||
D: HM-55291) for the pay period of January 1=E2=80=9315, 2025 (p. 1).
|
||||
|
||||
This Compensation Record contains sensitive information including:
|
||||
Net Pay: $2,360.96 (Gross: $3,358.00) (p. 1).
|
||||
Tax Deductions: Federal Tax ($412.00) and State Tax ($128.00) (p. 1).
|
||||
Banking Details: Direct Deposit to Evergreen Credit Union (Account ending i=
|
||||
n **** 4429) (p. 1).
|
||||
Employer Info: Hyperbolic Mortgage, Inc., Seattle, WA (p. 1).
|
||||
This document is marked as "Confidential Payroll Information =E2=80=94 For Employ=
|
||||
ee Use Only. Do Not Distribute." (p. 2). Please handle this with the highest=
|
||||
level of security.
|
||||
|
||||
=20
|
||||
|
||||
Regards,
|
||||
|
||||
Lukey
|
||||
Loan Processing Department (p. 1)
|
||||
Hyperbolic Mortgage
|
||||
|
||||
=20
|
||||
|
||||
|
||||
--B_3856698950_1611823738
|
||||
Content-type: text/html;
|
||||
charset="UTF-8"
|
||||
Content-transfer-encoding: quoted-printable
|
||||
|
||||
<html xmlns:v=3D"urn:schemas-microsoft-com:vml" xmlns:o=3D"urn:schemas-microsof=
|
||||
t-com:office:office" xmlns:w=3D"urn:schemas-microsoft-com:office:word" xmlns:m=
|
||||
=3D"http://schemas.microsoft.com/office/2004/12/omml" xmlns=3D"http://www.w3.org=
|
||||
/TR/REC-html40">
|
||||
<head>
|
||||
<meta http-equiv=3D"Content-Type" content=3D"text/html; charset=3Dutf-8">
|
||||
<meta name=3D"Generator" content=3D"Microsoft Word 15 (filtered medium)">
|
||||
<style><!--
|
||||
/* Font Definitions */
|
||||
@font-face
|
||||
{font-family:Wingdings;
|
||||
panose-1:5 0 0 0 0 0 0 0 0 0;}
|
||||
@font-face
|
||||
{font-family:PMingLiU;
|
||||
panose-1:2 2 5 0 0 0 0 0 0 0;}
|
||||
@font-face
|
||||
{font-family:"Cambria Math";
|
||||
panose-1:2 4 5 3 5 4 6 3 2 4;}
|
||||
@font-face
|
||||
{font-family:DengXian;
|
||||
panose-1:2 1 6 0 3 1 1 1 1 1;}
|
||||
@font-face
|
||||
{font-family:Aptos;}
|
||||
@font-face
|
||||
{font-family:"\@DengXian";
|
||||
panose-1:2 1 6 0 3 1 1 1 1 1;}
|
||||
@font-face
|
||||
{font-family:"\@PMingLiU";
|
||||
panose-1:2 1 6 1 0 1 1 1 1 1;}
|
||||
/* Style Definitions */
|
||||
p.MsoNormal, li.MsoNormal, div.MsoNormal
|
||||
{margin:0in;
|
||||
font-size:12.0pt;
|
||||
font-family:"Aptos",sans-serif;
|
||||
mso-ligatures:standardcontextual;}
|
||||
span.EmailStyle17
|
||||
{mso-style-type:personal-compose;
|
||||
font-family:"Aptos",sans-serif;
|
||||
color:windowtext;}
|
||||
.MsoChpDefault
|
||||
{mso-style-type:export-only;}
|
||||
@page WordSection1
|
||||
{size:8.5in 11.0in;
|
||||
margin:1.0in 1.0in 1.0in 1.0in;}
|
||||
div.WordSection1
|
||||
{page:WordSection1;}
|
||||
/* List Definitions */
|
||||
@list l0
|
||||
{mso-list-id:1602491166;
|
||||
mso-list-template-ids:1269587156;}
|
||||
@list l0:level1
|
||||
{mso-level-number-format:bullet;
|
||||
mso-level-text:\F0B7;
|
||||
mso-level-tab-stop:.5in;
|
||||
mso-level-number-position:left;
|
||||
text-indent:-.25in;
|
||||
mso-ansi-font-size:10.0pt;
|
||||
font-family:Symbol;}
|
||||
@list l0:level2
|
||||
{mso-level-number-format:bullet;
|
||||
mso-level-text:o;
|
||||
mso-level-tab-stop:1.0in;
|
||||
mso-level-number-position:left;
|
||||
text-indent:-.25in;
|
||||
mso-ansi-font-size:10.0pt;
|
||||
font-family:"Courier New";
|
||||
mso-bidi-font-family:"Times New Roman";}
|
||||
@list l0:level3
|
||||
{mso-level-number-format:bullet;
|
||||
mso-level-text:\F0A7;
|
||||
mso-level-tab-stop:1.5in;
|
||||
mso-level-number-position:left;
|
||||
text-indent:-.25in;
|
||||
mso-ansi-font-size:10.0pt;
|
||||
font-family:Wingdings;}
|
||||
@list l0:level4
|
||||
{mso-level-number-format:bullet;
|
||||
mso-level-text:\F0A7;
|
||||
mso-level-tab-stop:2.0in;
|
||||
mso-level-number-position:left;
|
||||
text-indent:-.25in;
|
||||
mso-ansi-font-size:10.0pt;
|
||||
font-family:Wingdings;}
|
||||
@list l0:level5
|
||||
{mso-level-number-format:bullet;
|
||||
mso-level-text:\F0A7;
|
||||
mso-level-tab-stop:2.5in;
|
||||
mso-level-number-position:left;
|
||||
text-indent:-.25in;
|
||||
mso-ansi-font-size:10.0pt;
|
||||
font-family:Wingdings;}
|
||||
@list l0:level6
|
||||
{mso-level-number-format:bullet;
|
||||
mso-level-text:\F0A7;
|
||||
mso-level-tab-stop:3.0in;
|
||||
mso-level-number-position:left;
|
||||
text-indent:-.25in;
|
||||
mso-ansi-font-size:10.0pt;
|
||||
font-family:Wingdings;}
|
||||
@list l0:level7
|
||||
{mso-level-number-format:bullet;
|
||||
mso-level-text:\F0A7;
|
||||
mso-level-tab-stop:3.5in;
|
||||
mso-level-number-position:left;
|
||||
text-indent:-.25in;
|
||||
mso-ansi-font-size:10.0pt;
|
||||
font-family:Wingdings;}
|
||||
@list l0:level8
|
||||
{mso-level-number-format:bullet;
|
||||
mso-level-text:\F0A7;
|
||||
mso-level-tab-stop:4.0in;
|
||||
mso-level-number-position:left;
|
||||
text-indent:-.25in;
|
||||
mso-ansi-font-size:10.0pt;
|
||||
font-family:Wingdings;}
|
||||
@list l0:level9
|
||||
{mso-level-number-format:bullet;
|
||||
mso-level-text:\F0A7;
|
||||
mso-level-tab-stop:4.5in;
|
||||
mso-level-number-position:left;
|
||||
text-indent:-.25in;
|
||||
mso-ansi-font-size:10.0pt;
|
||||
font-family:Wingdings;}
|
||||
ol
|
||||
{margin-bottom:0in;}
|
||||
ul
|
||||
{margin-bottom:0in;}
|
||||
--></style><!--[if gte mso 9]><xml>
|
||||
<o:shapedefaults v:ext=3D"edit" spidmax=3D"1026" />
|
||||
</xml><![endif]--><!--[if gte mso 9]><xml>
|
||||
<o:shapelayout v:ext=3D"edit">
|
||||
<o:idmap v:ext=3D"edit" data=3D"1" />
|
||||
</o:shapelayout></xml><![endif]-->
|
||||
</head>
|
||||
<body lang=3D"EN-US" link=3D"#467886" vlink=3D"#96607D" style=3D"word-wrap:break-wo=
|
||||
rd">
|
||||
<div class=3D"WordSection1">
|
||||
<p class=3D"MsoNormal">Hi,<span style=3D"mso-fareast-language:ZH-TW"><o:p></o:p=
|
||||
></span></p>
|
||||
<p class=3D"MsoNormal"><span style=3D"mso-fareast-language:ZH-TW"><o:p> </=
|
||||
o:p></span></p>
|
||||
<p class=3D"MsoNormal">I am forwarding the <b>Payroll Statement</b> =
|
||||
;for employee <b>Daniel Reyes</b> (<b>Employee ID: HM-55291</b>) f=
|
||||
or the pay period of January 1=E2=80=9315, 2025 (p. 1).<o:p></o:p></p>
|
||||
<p class=3D"MsoNormal">This <b>Compensation Record</b> contains sen=
|
||||
sitive information including:<o:p></o:p></p>
|
||||
<ul style=3D"margin-top:0in" type=3D"disc">
|
||||
<li class=3D"MsoNormal" style=3D"mso-list:l0 level1 lfo1"><b>Net Pay:</b> =
|
||||
$2,360.96 (Gross: $3,358.00) (p. 1).<o:p></o:p></li><li class=3D"MsoNormal" st=
|
||||
yle=3D"mso-list:l0 level1 lfo1"><b>Tax Deductions:</b> Federal Tax ($412.=
|
||||
00) and State Tax ($128.00) (p. 1).<o:p></o:p></li><li class=3D"MsoNormal" sty=
|
||||
le=3D"mso-list:l0 level1 lfo1"><b>Banking Details:</b> Direct Deposit to&=
|
||||
nbsp;<b>Evergreen Credit Union</b> (Account ending in **** 4429) (p. 1)=
|
||||
.<o:p></o:p></li><li class=3D"MsoNormal" style=3D"mso-list:l0 level1 lfo1"><b>Em=
|
||||
ployer Info:</b> Hyperbolic Mortgage, Inc., Seattle, WA (p. 1).<o:p></o=
|
||||
:p></li></ul>
|
||||
<p class=3D"MsoNormal">This document is marked as <b>"Confidential =
|
||||
Payroll Information =E2=80=94 For Employee Use Only. Do Not Distribute."</b>&=
|
||||
nbsp;(p. 2). Please handle this with the highest level of security.<span sty=
|
||||
le=3D"mso-fareast-language:ZH-TW"><o:p></o:p></span></p>
|
||||
<p class=3D"MsoNormal"><span style=3D"mso-fareast-language:ZH-TW"><o:p> </=
|
||||
o:p></span></p>
|
||||
<p class=3D"MsoNormal">Regards,<span style=3D"mso-fareast-language:ZH-TW"><o:p>=
|
||||
</o:p></span></p>
|
||||
<p class=3D"MsoNormal"><span style=3D"mso-fareast-language:ZH-TW">Lukey<br>
|
||||
</span>Loan Processing Department (p. 1)<br>
|
||||
<b>Hyperbolic Mortgage</b><o:p></o:p></p>
|
||||
<p class=3D"MsoNormal"><o:p> </o:p></p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
--B_3856698950_1611823738--
|
||||
|
||||
|
||||
--B_3856698950_3788612792
|
||||
Content-type: application/pdf; name="Daniel_Reyes_Payroll_Jan2025.pdf";
|
||||
x-mac-creator="4F50494D";
|
||||
x-mac-type="50444620"
|
||||
Content-disposition: attachment;
|
||||
filename="Daniel_Reyes_Payroll_Jan2025.pdf"
|
||||
Content-transfer-encoding: base64
|
||||
|
||||
|
||||
JVBERi0xLjQKJZOMi54gUmVwb3J0TGFiIEdlbmVyYXRlZCBQREYgZG9jdW1lbnQgaHR0cDov
|
||||
L3d3dy5yZXBvcnRsYWIuY29tCjEgMCBvYmoKPDwKL0YxIDIgMCBSIC9GMiAzIDAgUgo+Pgpl
|
||||
bmRvYmoKMiAwIG9iago8PAovQmFzZUZvbnQgL0hlbHZldGljYSAvRW5jb2RpbmcgL1dpbkFu
|
||||
c2lFbmNvZGluZyAvTmFtZSAvRjEgL1N1YnR5cGUgL1R5cGUxIC9UeXBlIC9Gb250Cj4+CmVu
|
||||
ZG9iagozIDAgb2JqCjw8Ci9CYXNlRm9udCAvSGVsdmV0aWNhLUJvbGQgL0VuY29kaW5nIC9X
|
||||
aW5BbnNpRW5jb2RpbmcgL05hbWUgL0YyIC9TdWJ0eXBlIC9UeXBlMSAvVHlwZSAvRm9udAo+
|
||||
PgplbmRvYmoKNCAwIG9iago8PAovQ29udGVudHMgOSAwIFIgL01lZGlhQm94IFsgMCAwIDYx
|
||||
MiA3OTIgXSAvUGFyZW50IDggMCBSIC9SZXNvdXJjZXMgPDwKL0ZvbnQgMSAwIFIgL1Byb2NT
|
||||
ZXQgWyAvUERGIC9UZXh0IC9JbWFnZUIgL0ltYWdlQyAvSW1hZ2VJIF0KPj4gL1JvdGF0ZSAw
|
||||
IC9UcmFucyA8PAoKPj4gCiAgL1R5cGUgL1BhZ2UKPj4KZW5kb2JqCjUgMCBvYmoKPDwKL0Nv
|
||||
bnRlbnRzIDEwIDAgUiAvTWVkaWFCb3ggWyAwIDAgNjEyIDc5MiBdIC9QYXJlbnQgOCAwIFIg
|
||||
L1Jlc291cmNlcyA8PAovRm9udCAxIDAgUiAvUHJvY1NldCBbIC9QREYgL1RleHQgL0ltYWdl
|
||||
QiAvSW1hZ2VDIC9JbWFnZUkgXQo+PiAvUm90YXRlIDAgL1RyYW5zIDw8Cgo+PiAKICAvVHlw
|
||||
ZSAvUGFnZQo+PgplbmRvYmoKNiAwIG9iago8PAovUGFnZU1vZGUgL1VzZU5vbmUgL1BhZ2Vz
|
||||
IDggMCBSIC9UeXBlIC9DYXRhbG9nCj4+CmVuZG9iago3IDAgb2JqCjw8Ci9BdXRob3IgKFwo
|
||||
YW5vbnltb3VzXCkpIC9DcmVhdGlvbkRhdGUgKEQ6MjAyNTExMjEwMjM4MTcrMDAnMDAnKSAv
|
||||
Q3JlYXRvciAoXCh1bnNwZWNpZmllZFwpKSAvS2V5d29yZHMgKCkgL01vZERhdGUgKEQ6MjAy
|
||||
NTExMjEwMjM4MTcrMDAnMDAnKSAvUHJvZHVjZXIgKFJlcG9ydExhYiBQREYgTGlicmFyeSAt
|
||||
IHd3dy5yZXBvcnRsYWIuY29tKSAKICAvU3ViamVjdCAoXCh1bnNwZWNpZmllZFwpKSAvVGl0
|
||||
bGUgKFwoYW5vbnltb3VzXCkpIC9UcmFwcGVkIC9GYWxzZQo+PgplbmRvYmoKOCAwIG9iago8
|
||||
PAovQ291bnQgMiAvS2lkcyBbIDQgMCBSIDUgMCBSIF0gL1R5cGUgL1BhZ2VzCj4+CmVuZG9i
|
||||
ago5IDAgb2JqCjw8Ci9GaWx0ZXIgWyAvQVNDSUk4NURlY29kZSAvRmxhdGVEZWNvZGUgXSAv
|
||||
TGVuZ3RoIDEzODYKPj4Kc3RyZWFtCkdhdG07YWA/LHEmQUBya3FPaUoyWjVFXUhcQklbVWpP
|
||||
Ym8iWidcLls4ZiprSCd1aktAJj09P11mMiUkZyg2WXNSMVNwZzoyLiY5YEpGODEuIT80OCFy
|
||||
L09bKlpPZjwsJjBNXCgtUXBXQiY3MzcqNFNuWiplNEBtamYvJGdBZnFoRUZObU5jbU9mL1hy
|
||||
TUlAbkVoaDddUSJDXElQIVk4WG45LFFQXl4tQ0NZP2MsIXJLWDcwJEQkVU1BNFpMM2ddc3NO
|
||||
amNgPjFcMG5fYjdxdT9jJiFeRVheL21odF1ISF9SNEp0JCQwUHV1LTRHPWlrNEBfUSVpT24n
|
||||
PWRtKl1MaWQsKSpMY0RvIlFiZVtlJCVdIVdBY0dWVXNDJCNmYD1vKzxCWT5XNj44RUNwPkxO
|
||||
OzNDZkdMSjBNZSlyI25sO2kkMHRyR2cqVHJoXSg8N2BObkQtTGciTTorRzZYQVE9RV9aX2Jr
|
||||
O1xfWzZLcUhdXSdGUjJNW10jUiptKyg5YztvQV1wWWxxVVYyRmtVZkRXJXMkRENkV2k1Vzlb
|
||||
VCljOEoqc3NiPFxuO1ZEJkNfY3BUJnJtWXBkOWohZ0M4Lm9WXzlmMCRhWS9oUlNnNVJuLkVt
|
||||
K0YnVW86IVI5MChqNHJuZVwtbjhHNClYbERBIjpANio+OkokRSshMihnNmJ1Ky1qYFprRzEo
|
||||
ZHBMRlBsN3BvT1s+Xz1MPiVORnRSaFxHR008OTZYZ19EaDQ3I1RhIj9DRCxDaFJkLz4pQltA
|
||||
ZypbKFJhLTZEJjBPTy9lPXVXRWBGQU9SMiRlJEBQaVduRThNXiVxOThEI0ssUWMiWjNRSk9r
|
||||
PzIrUXIucHFgZixiKFRlcl5TaHBJY3RWVnQoQnBjVS1IZW5hJUNDKURrcSYwTU5XaGFEJGJe
|
||||
KG5RaXRlPltYS00zaypWaFZfTVJySVopKmQodVFbMyZYPU1oI2VYQiNzLTg5X1dWWWpeZi5D
|
||||
Nk9bWlFWVyNpN1lqWiducjQ8NEQqbWY6bSNHcyo9YlwpNCZeNU5ERy5hUG8pYT5WKygzcW5q
|
||||
ZS8iYi9CZVJbLXMyJWdnSFE+QFpCcENkRyhmKkMjVjkyTEsxU0dXOFlcSHRbPlY+TUpZQFoo
|
||||
WFdkMkxNSD5gMzBBInBOQCpPTyVkI1FHJCY8NHBZLEQ9WChAN2BCYzQpS108bU9UVSY9ZHM3
|
||||
UyZlRDtuNEklUE8qZnQyOyRlYFRxRyM4JGI7S28sUzVqSkxXP0MhLD1XMG5AUCpGWE5QXXMj
|
||||
P0IjcDozXDFCODpzSlNbN1k1XCEnZTdkJXAkSFhNTkhobk5oKUFtMzdQPF5sVnVfVDJpPk1g
|
||||
OyFtO1lzTilrRnE5YnVkUGBBJkYuUjkmaWZANWAhX0ZKQmUuNC1vYnVESG9lcmtUPWMvWFVB
|
||||
XS4iOGBMcGBeUDJdKk5KI3VxXyxwK1AuN1lNbEZwRWgpcEVLPURnTiwvWF1qSXRYYVFCJ3Ml
|
||||
OjI7JDhkLVYuXDpzODpWVE4rbEZbInBtaUxtdTVIMCVEMkZnV1JgdTZyYyVXP0U7WTxORDlO
|
||||
SHI3O3FdPkpTbjE6KCUzVCFIYjVAZVZFSlUkVEVHalloKSpqWSV0RzIoOUk/YkROdDdway43
|
||||
TktvR1oxNEk/c1owbCtFLUYyZztoW0VfSFEnKm5URituMihpRzcjL2JGXF9aTU4ialcvNmNS
|
||||
cmY4Wy1bVCFzLFZeSWEmOj4/L1ByVCQ3YzNUIlg6W11EZTp1STszQlheNC9zLD1OSFdUMSlw
|
||||
c2IsQ0dcalNWVW8wbTZwanM6MS5ESFcxXnQtUUFrISlCVXRnbltnPDEkWiRSaSk4LGlXaCsl
|
||||
S2Z+PmVuZHN0cmVhbQplbmRvYmoKMTAgMCBvYmoKPDwKL0ZpbHRlciBbIC9BU0NJSTg1RGVj
|
||||
b2RlIC9GbGF0ZURlY29kZSBdIC9MZW5ndGggMjE2Cj4+CnN0cmVhbQpHYXInYjBha2lQJ0Vt
|
||||
PldeWiVfKlcoZVxCWTFhV287JSVBYldMP0YpYypNKyhxWDcuOjM2OG9UTm9ULz5gJCdOPlto
|
||||
ZFVlLzUkSSorPDBlcThnKmpWLGpRPEkwIWM7QEZWO11xcUozRVpDY2BELlNVN1Nrblhnb0Ey
|
||||
VjgnblNucC4oWlJzaiJoaipsRzJFOTY+LEBkc2ElQ3JxQFJPZydYQTE1UW5CTz9KdVYvWWk7
|
||||
Tm4rVU5FbCpBaUVudSk/WUFeVj0zVExpbzBuKkFdSjouRT0qXVc5fj5lbmRzdHJlYW0KZW5k
|
||||
b2JqCnhyZWYKMCAxMQowMDAwMDAwMDAwIDY1NTM1IGYgCjAwMDAwMDAwNzMgMDAwMDAgbiAK
|
||||
MDAwMDAwMDExNCAwMDAwMCBuIAowMDAwMDAwMjIxIDAwMDAwIG4gCjAwMDAwMDAzMzMgMDAw
|
||||
MDAgbiAKMDAwMDAwMDUyNiAwMDAwMCBuIAowMDAwMDAwNzIwIDAwMDAwIG4gCjAwMDAwMDA3
|
||||
ODggMDAwMDAgbiAKMDAwMDAwMTA3MSAwMDAwMCBuIAowMDAwMDAxMTM2IDAwMDAwIG4gCjAw
|
||||
MDAwMDI2MTMgMDAwMDAgbiAKdHJhaWxlcgo8PAovSUQgCls8YzNkM2Q3MDA0MTgzM2I0MzE5
|
||||
Y2VhOTIwMmY1MzVhZDE+PGMzZDNkNzAwNDE4MzNiNDMxOWNlYTkyMDJmNTM1YWQxPl0KJSBS
|
||||
ZXBvcnRMYWIgZ2VuZXJhdGVkIFBERiBkb2N1bWVudCAtLSBkaWdlc3QgKGh0dHA6Ly93d3cu
|
||||
cmVwb3J0bGFiLmNvbSkKCi9JbmZvIDcgMCBSCi9Sb290IDYgMCBSCi9TaXplIDExCj4+CnN0
|
||||
YXJ0eHJlZgoyOTIwCiUlRU9GCg==
|
||||
--B_3856698950_3788612792--
|
||||
|
||||
127
data/Q1 2026 Campaign Mockups - Design Review.eml
Normal file
127
data/Q1 2026 Campaign Mockups - Design Review.eml
Normal file
@ -0,0 +1,127 @@
|
||||
Content-Type: multipart/mixed; boundary="===============1579291375177179857=="
|
||||
MIME-Version: 1.0
|
||||
From: "Sarah Chen (Boardware)" <sarah.chen@boardware.com>
|
||||
To: "Michael Wang (Boardware)" <michael.wang@boardware.com>
|
||||
Subject: Q1 2026 Campaign Mockups - Design Review
|
||||
Date: Thu, 19 Mar 2026 03:22:18 +0800
|
||||
Message-ID: <campaign2026design@boardware.com>
|
||||
MIME-Version: 1.0
|
||||
X-MS-Has-Attach: yes
|
||||
|
||||
--===============1579291375177179857==
|
||||
Content-Type: multipart/alternative; boundary="===============4113167398364688896=="
|
||||
MIME-Version: 1.0
|
||||
|
||||
--===============4113167398364688896==
|
||||
Content-Type: text/plain; charset="utf-8"
|
||||
MIME-Version: 1.0
|
||||
Content-Transfer-Encoding: base64
|
||||
|
||||
SGkgTWljaGFlbCwKClBsZWFzZSBmaW5kIGF0dGFjaGVkIHRoZSBtb2NrdXBzIGZvciB0aGUgUTEg
|
||||
MjAyNiBjYW1wYWlnbi4gSSd2ZSBpbmNsdWRlZCB0aGUgcHJpbWFyeSBkZXNpZ24gZGlyZWN0aW9u
|
||||
IHdpdGggY29sb3IgdmFyaWF0aW9ucy4KCktleSBwb2ludHM6Ci0gRGVzaWduIGZvbGxvd3MgdGhl
|
||||
IHVwZGF0ZWQgYnJhbmQgZ3VpZGVsaW5lcwotIFJlc3BvbnNpdmUgbGF5b3V0IHRlc3RlZCBhY3Jv
|
||||
c3MgZGV2aWNlcwotIEFsbCBhc3NldHMgYXJlIGluIGhpZ2ggcmVzb2x1dGlvbiAoMzAwIERQSSkK
|
||||
ClBsZWFzZSByZXZpZXcgYW5kIHByb3ZpZGUgZmVlZGJhY2sgYnkgZW5kIG9mIHdlZWsuCgpCZXN0
|
||||
IHJlZ2FyZHMsClNhcmFoIENoZW4KRGVzaWduIExlYWQ=
|
||||
|
||||
--===============4113167398364688896==
|
||||
Content-Type: text/html; charset="utf-8"
|
||||
MIME-Version: 1.0
|
||||
Content-Transfer-Encoding: base64
|
||||
|
||||
PGh0bWw+CjxoZWFkPgo8c3R5bGU+CmJvZHkgeyBmb250LWZhbWlseTogQXB0b3MsIHNhbnMtc2Vy
|
||||
aWY7IH0KPC9zdHlsZT4KPC9oZWFkPgo8Ym9keT4KPHA+SGkgTWljaGFlbCw8L3A+CjxwPlBsZWFz
|
||||
ZSBmaW5kIGF0dGFjaGVkIHRoZSBtb2NrdXBzIGZvciB0aGUgUTEgMjAyNiBjYW1wYWlnbi4gSSd2
|
||||
ZSBpbmNsdWRlZCB0aGUgcHJpbWFyeSBkZXNpZ24gZGlyZWN0aW9uIHdpdGggY29sb3IgdmFyaWF0
|
||||
aW9ucy48L3A+CjxwPjxzdHJvbmc+S2V5IHBvaW50czo8L3N0cm9uZz48L3A+Cjx1bD4KPGxpPkRl
|
||||
c2lnbiBmb2xsb3dzIHRoZSB1cGRhdGVkIGJyYW5kIGd1aWRlbGluZXM8L2xpPgo8bGk+UmVzcG9u
|
||||
c2l2ZSBsYXlvdXQgdGVzdGVkIGFjcm9zcyBkZXZpY2VzPC9saT4KPGxpPkFsbCBhc3NldHMgYXJl
|
||||
IGluIGhpZ2ggcmVzb2x1dGlvbiAoMzAwIERQSSk8L2xpPgo8L3VsPgo8cD5QbGVhc2UgcmV2aWV3
|
||||
IGFuZCBwcm92aWRlIGZlZWRiYWNrIGJ5IGVuZCBvZiB3ZWVrLjwvcD4KPHA+QmVzdCByZWdhcmRz
|
||||
LDxici8+ClNhcmFoIENoZW48YnIvPgpEZXNpZ24gTGVhZDwvcD4KPC9ib2R5Pgo8L2h0bWw+
|
||||
|
||||
--===============4113167398364688896==--
|
||||
|
||||
--===============1579291375177179857==
|
||||
Content-Type: image/jpeg; name="Campaign_Mockup_Q1_2026.jpg"
|
||||
MIME-Version: 1.0
|
||||
Content-Transfer-Encoding: base64
|
||||
Content-Disposition: attachment; filename="Campaign_Mockup_Q1_2026.jpg"
|
||||
|
||||
/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAkGBxAPERAQEBMQDxATEBUQEBISEA8SEhUPFhIYFhYV
|
||||
FRMYHSghGBonHhUVITEhMSorLi4uFyAzODMwNyguLisBCgoKDg0OGhAQGy8lIB8uLS8tNy0rLS0t
|
||||
LS0tLSstLS0tLS0tLSstLS0tLS0tLS0tLS0tLSsrLS0tLSstLS0tLf/AABEIAOEA4QMBIgACEQED
|
||||
EQH/xAAcAAEAAgMBAQEAAAAAAAAAAAAABgcCBAUDAQj/xABKEAABAwIDAwYICggEBwAAAAABAAID
|
||||
BBEFEiEGMUEHEyJRYYEUFTJScZGToRczQlNUYpKxstEWI2NygqLS0yTBwvFDZHODlaSz/8QAGAEB
|
||||
AQEBAQAAAAAAAAAAAAAAAAECAwT/xAAiEQEBAQEAAgIBBQEAAAAAAAAAARECEiExQVETIjJCYQP/
|
||||
2gAMAwEAAhEDEQA/ALUREXF7xERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBE
|
||||
RAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERARF9QfEREBERARE
|
||||
QEREBERAREQEREBERAREQEREBERBzdosbioIH1EtyB0WMHlPkPksb+fAAngqubjmN15fPDI6KME5
|
||||
GRmONmnyW5heQ9ZOl+rcvXbOtdiuJNpI3HmIHGMkbswP66T0i2Qdo7VLIIWxtaxgDWNaGtaNwaNw
|
||||
WL7rHXWNPYfbl88vgdcAyovljflyZ3jex7dzX9RFgd1gbXnyqjbTAeeb4RCCJ4wC4NuHPY3UEW1z
|
||||
t3g79OwLawflUYyGNtTFNLM1uV0kZiyvtudYkWJFr8LpOs9Vrm6s1FX3wsUv0ep9cH9a1cT5VY3Q
|
||||
yCnhmZMWkRvkMWRpPyjZxJtqbddlfKflWe3m2E7pvF9ASJL5JZGeXznGNjvk2HlO4brixUeecVwv
|
||||
LUCd0rNOcBklljueEjH8PrC3pC6Gw2CmJhqZbmWUdDNcuEZ1zEnXM7eey3WVKJY2vaWuAc1wLXNO
|
||||
oLSLEFZy3253vK7mzGPRYhTtnj6JvlljvcxygatPrBB4ghdVUxhtY/A67XM+klHSA1Lor6EftGE9
|
||||
4P1haQV3KzCLiCmkeeBlkZEPU0OKs6n26RY6KqH8omKS/E0jGjr5mol/muAua/bzF3bnMZ+7BEPx
|
||||
XTzibF0oqUbttjA150HsMNOfuC6LOUHFoQHT08b2EAhxgljBB3dNpt7k84bFtIq+wvlVpn2FTDJT
|
||||
ni5h55nfYB3uKm2GYrT1Tc9PLHM3jkcCQepw3tPYVqWX4VtoiKgiIgIiICIiAiIgIig+1uB4vNUm
|
||||
WjqgyHI0Nj56SLKQOloGkOudb9tuClE4XI2txbwKjqJx5bWZY/8AqvORnvIPcoK7DtpotWyultwE
|
||||
tK//AOgCj21mMYpK2Okr2hjszZWNyMa956TGklhLSLk94Wb1g63J5h+WJ9Q7V0jsjSd+Rp1Pe6/2
|
||||
QpavDD6UQRRxDdGxrPSQNT3m571nUTsjaXyOaxjRdznGwAVkyOFu16KK0mz7hVl744zFne+5DC1w
|
||||
dewy9eo9S7+H4nBUAmGRsmXyrXBHVdpse9baqIqzZ53heYxR8zzhffKzLk35cvutZZVmzhNSHsji
|
||||
ERcx2gYA0NtcZe73rv8AjCHneY5xnPWzc3fpWtf7tbdS2UR9XxamIYnBTgGaRsebyb3JNt9mi5K9
|
||||
6aoZK0Pjc17Hatc03BU1ceddQQzgNmjbIAbgOG49YPBKahhi0jjjj/dY0H1gLDFp5I4ZHws52Rrb
|
||||
sZqbm4B0GpsLm3Gyh4jxmr1JdAw9ogHqHTUtxZNTxx6/fuUGxbBZIXHK1z4ibtc0E2HU4DcQvn6D
|
||||
TSazVDSePRkkPrcQplh9KIIo4g5zhGwMDnbzbiVZb+E6k/KFYdhEs7gMrmMv0nuBAA42vvKlOPUh
|
||||
fTFkY1Zlc1o3kN4Du+5dNFdTFXyQMfvAPbx9a+4bg9Q55fRuc2RgzZg8xuHU0PHE9R0NlYNRhFPI
|
||||
7M6NpcdSQXNue2xF1tQQMjaGsaGNHACwv1+lSyVZbHH2a5RXxv8ABsTBY4HLz+XKWn9swaW+sNOy
|
||||
2qspjg4Agggi4IIIIO4g8Qq+x3BIqxln9F4+LkA6Teztb2fcuJsptHNhE3gVYb0pPRdqRHc6SMPz
|
||||
Z4jhqesGbZ8u3PWrdRGkEAjUHUEbiEW2hERAREQEREBV3jWK7QGeaOnpw2JsjmxObFG7NHfou5x7
|
||||
rEkWO4WvZWIilgq621DvOb/48KPYd4TV4mzwx3OzQkiUnm9BCTZvQGXR596uytqRDHJK7yY2OkPo
|
||||
a0uP3KoOTqJz3VNQ/V7iGk/XcTI/3lqxefcZ69RNlw9scNlqafLFq9sgkyXAzgAiwvx1v3LuItWa
|
||||
4y4hWxGCVEMz5pWuibzZYGutmcSQd3ACykG0mITU0POQx86c4DrhxDWWN3EN14Adl11UUnOTIt62
|
||||
7VW+NZfCvD+ZNs97Wfzd+byW5y2+2qnuzWIzVMJkmjERzkNsHAOZYdIB2u8kdtl1rok5xb1v0hW3
|
||||
GCVE0rJomulbzYjLW2zNIcTe3EG/uXX2NwyWmpy2XRz5DJkuDlBAFjbS+l+9d1E8Zup5eseVU17m
|
||||
PEbsjyxwY4i4a8jQ29KhfiDFjvqf/am/JTlFbNJ1iDeLcZi1bK6TsE4f7pApXgjqgwsNUA2bXMBl
|
||||
3X6NwNAbLeRSc4XrRERaZEREBcnaXBm1kJboJW3dC7qdxaT5p3eo8F1kUs1Zcczkp2hL2OoJriSA
|
||||
Xhvv5kGzoz2sNh6COpWEqY2kLsPr4K6MaOdneBxc3SVv8TD6yVcsMrXta9pzNc0OaetpFwVOb9O8
|
||||
uslX+3u3UlNJ4JR5TMPjpCA7I47mMbuLraknQaab7SDbXaVmHU5fo6d9207Ot/FxHmtvc9w4qqsL
|
||||
wCokiNWf1jpHOeAfjHAm5k7S4kn/AHVt+odXI6mE8otdTyN8LtUQk9PoMbIG8SxzAASOojXsVvxS
|
||||
Ne1rmkOa4BzSNxaRcEL88Yu8BoB3gkkcdBbcr62fpXQ0tLE/y46eNjv3msAPvUnzYnN2a30RFtoR
|
||||
EQR3lDqObw2rPnRiL2j2s+5xUR2Dhy0bXefI9/qOQfhXb5XZstAG/OVMbfUHP/0BaOy8eWjph+yD
|
||||
vtdL/NY/sx38OoiItOIiIgIiICIiAiIgIiICIiAiIgIiIOHtpR87SSH5UVpm/wAPlfyly8NnuUKG
|
||||
kw6KJ7XzVMYdGxgBa3mwTzZdIdAMpA0uejuUhljD2uadQ5paR2EWP3qsqCmjhkY5zQ/K8Eh9nCwO
|
||||
um5Zy7sb57kntjXz1FdMamruS9oLRlLW83c5WxtO5m/0679SpbhO0bBG4VBDDGy4dbymgbrD5XZx
|
||||
W3tZDGYC97msLNWOcbX62Drv1ddlCsFwmfFJhDCMsbSDJIRdsbfOd1u32bx9FyHrmf6e+r/ju7F4
|
||||
Y7Fa99ZK21PC8PI0sZBbmo+0jRzj6POVwrSwbC4qOFlPCMrGDvc46uc48XE6lbismOwiItAiIgr7
|
||||
lod/haUf83f1QSfmvbBm2p6cdUEf4AvLlnZelpj1Vdj3wSfkvXBX5qanPXBH+ALH9nP/AKfDcREW
|
||||
nIREQEREBERAREQEREBERAREQEREH0KqTNLPI5lPE+UlzrBjHPdbMdbNGitKZ+Vrnea1zvULrici
|
||||
kRtWydZhZ3gPcfxBZu7kdOOZWrhXJ9W1bmy4jK6JoGjMzXzZeoW6EY9foVmYThcNJE2GBgjjbwFy
|
||||
SeLnOOrnHrK20VkkdRERaBERAREQQ/lWp8+HPcNTHNFJ3Zsh9zyuRshNnoqf6rTGf4HFv3AKcY/Q
|
||||
eFUtRBxkhexvY8t6J9dlV/JxWXjmgNw5jxIAd4DhlcO4t/mWL/Jjv4TBERacRERAREQEREBERARE
|
||||
QEREBERAREQcraqp5qjqHbiYzGPS85P81v8AJHSZKAycZp5H/wALbRD8BUT5Q6skQUrOk97ucLRv
|
||||
Jvkjb3kn1K1MEw8UtPBTj/hRNYT1uA6R7zc96zP5O3E9N1ERbbEREBERAREQfVT21lI7CcTFSwHw
|
||||
ecuksN3SP66P0gkPHpHUVcC5G1eAsxCmfA6zX+XC8i+SYeSfRqQewlZ6mwrixSNe1rmkOa4BzSNx
|
||||
aRcELJQrZTF3UsjqCrvG5jyxmb5D7/Fk+ab3ad2vUQpqkuuFmUREVZEREBERAREQEREBERAREQFj
|
||||
I8NBc4hrWgucTuDQLkrJQ7bXFnSEUNODJI9zWyBupLiejEO0mxP+6luRqTaz2IpTiWJvrHg8zARI
|
||||
0Hdm1EDPSLF57W9qt1cXZDAW4fSsh0Mh/WTuHypiBe3YLBo7AF2k5mR3ERFoEREBERAREQEREEW2
|
||||
22MjxFudpEVU1tmyEdF7eDJAN46jvF+IuDAKPHarDZPBa+N5DfJO94Zuux26Vnfce5XQorylYPJV
|
||||
0RELc8kUjZmtAu5zQHNc1vbZ17cctljqfcSzWm2sjMYmztERYH53HK3IdxN929avj+j+kQe0aoHN
|
||||
j7p6WOgbETICxnRJc52Q3aBHa+bQX9CnOFcl1MYYjUuqGzlgMrWSR5GvOpaLsO7dv4Kbb8MTj8s/
|
||||
H1H9Ig9qxZePKT6RT+1Z+a9TyV0HzlWP+5D/AG1xtq+TmGmpZJ6V1TLJHlcWOMbgY79MgNYCSBr3
|
||||
FX9x4RIHyta0vLmtYBmLiQGhvXfdZafjuk+kQe1j/NQOp2jbJQR0bWOzgMYXXBaWtdduUDUk2aLW
|
||||
UywbkvgfBC+pfUxzuYHSMY6ENa465bFhNwLA677qeVvwThteO6T6RB7WP808d0n0iD2sf5rL4KqH
|
||||
56r+3B/bT4KqH56r+3B/bT9x+mx8d0n0iD2sf5r1psSgldljlikda+VsjHGw42BWHwVUXz1X9uD+
|
||||
2ovtNsxJhFRDU0zZZ6dtjmf0i2TUOa8sAytLTobcT3t6h+mlVXilPCcsssUbrXyueAbddl4/pBR/
|
||||
SIPaNUZ2ewOTGax888ckVLlu5zejqGhrGMe4dI8SbcDuuFMPguw/zqr2sf8AQktp4RqfpBR/SIPa
|
||||
NXtS4rTzOyRTRSOtfK17SbDfovX4LsP86q9rH/QoztHsZUYdPFU4eyWoiaL2I52RkliCHNaAXNIP
|
||||
Aaa9iu9Q8Iy2l2kkbIaSla505IaXBtyHOAIbG3i6xGvD7pHyf7E+B/4qqs6rcDlbfMIWu39L5Uhv
|
||||
q7huHEnk7A4DWSVzsRq43QANcWhzSxzpHNyC0Z1DQ2+/s36qzU5m+63zMERFtRERAREQEREBERAR
|
||||
EQEREGIiaCXBrQ473WGY96yREBfV8RBpxYTTMkMzYIGynUyNhjD79eYC63ERAREQF9XxEH1fERAR
|
||||
EQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREB
|
||||
ERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQ
|
||||
EREBERAREQEREBERB//Z
|
||||
|
||||
--===============1579291375177179857==--
|
||||
30
data/test_zip_attachment.eml
Normal file
30
data/test_zip_attachment.eml
Normal file
@ -0,0 +1,30 @@
|
||||
Subject: Test Zip Attachment
|
||||
From: tester@example.com
|
||||
To: reviewer@example.com
|
||||
MIME-Version: 1.0
|
||||
Content-Type: multipart/mixed; boundary="===============2899700414055881751=="
|
||||
|
||||
--===============2899700414055881751==
|
||||
Content-Type: text/plain; charset="utf-8"
|
||||
Content-Transfer-Encoding: 7bit
|
||||
|
||||
Hi,
|
||||
|
||||
Please check the attached zip file.
|
||||
|
||||
Thanks.
|
||||
|
||||
--===============2899700414055881751==
|
||||
Content-Type: application/zip
|
||||
Content-Transfer-Encoding: base64
|
||||
Content-Disposition: attachment; filename="test_data.zip"
|
||||
MIME-Version: 1.0
|
||||
|
||||
UEsDBBQAAAAAACyJc1zvlIadNgAAADYAAAAKAAAAc2VjcmV0LnR4dENPTkZJREVOVElBTDogVEhJ
|
||||
UyBJUyBBIFNFQ1JFVCBEVU1NWSBaSVAgVEVTVCBGT1IgRExQLlBLAwQUAAAAAAAsiXNcV7unaSkA
|
||||
AAApAAAABwAAAGNvZGUucHlkZWYgbXlfc2VjcmV0KCk6CiAgICByZXR1cm4gJ3Bhc3N3b3JkMTIz
|
||||
J1BLAQIUAxQAAAAAACyJc1zvlIadNgAAADYAAAAKAAAAAAAAAAAAAACAAQAAAABzZWNyZXQudHh0
|
||||
UEsBAhQDFAAAAAAALIlzXFe7p2kpAAAAKQAAAAcAAAAAAAAAAAAAAIABXgAAAGNvZGUucHlQSwUG
|
||||
AAAAAAIAAgBtAAAArAAAAAAA
|
||||
|
||||
--===============2899700414055881751==--
|
||||
51
docs/email-dlp-workflow.md
Normal file
51
docs/email-dlp-workflow.md
Normal file
@ -0,0 +1,51 @@
|
||||
[ Directory of .eml Files ]
|
||||
│
|
||||
▼
|
||||
┌───────────────┐
|
||||
│ CLI (cli.py) ├────────────────────────────────────────────────┐
|
||||
└───────┬───────┘ │
|
||||
│ Loop through EMLs │
|
||||
▼ │
|
||||
┌───────────────┐ Metadata (Subject, From, To, Date) │
|
||||
│ EML Parser │──────────────────────────────────────────┐ │
|
||||
│ (parser.py) │ │ │
|
||||
├───────────────┤ Body Text (Parsed via bs4) │ │
|
||||
│ - Decode Heads├────────────────────────────────────┐ │ │
|
||||
│ - Extract HTML│ │ │ │
|
||||
│ - Save Attach │ Attachment Files ▼ ▼ ▼
|
||||
│ to Temp Dir ├─────────────┐ ┌────────────────────────┐
|
||||
└───────────────┘ │ │ Prompt Builder │
|
||||
▼ │ (analyzer.py) │
|
||||
┌───────────────┐ ┌───────────────┐ ├────────────────────────┤
|
||||
│ Converter │◄────┤ Temporary Dir │ │ System Prompt: │
|
||||
│(converter.py) │ └───────────────┘ │ - DLP Policy rules │
|
||||
├───────────────┤ │ (from policy.py) │
|
||||
│ - MarkItDown │ │ - Schemas & Thresholds │
|
||||
│ - Zip/7z Loop │ │ │
|
||||
│ - PyMuPDF Ext │ │ User Prompt: │
|
||||
│ - Text Extrac │ │ - Email Metadata │
|
||||
│ - Image Base64│ │ - Body Text │
|
||||
└───────┬───────┘ │ - Multimodal list │
|
||||
│ │ (Texts + Image URLs) │
|
||||
│ Converted Texts & IMAGE_SENTINELs └───────────┬────────────┘
|
||||
└─────────────────────────────────────────────────► │ Use OpenAI-comp
|
||||
│ API (vLLM/Qwen)
|
||||
┌────────────────────────┐ (or simulator)
|
||||
│ LLM Inference │
|
||||
└───────────┬────────────┘
|
||||
│
|
||||
┌───────────▼────────────┐
|
||||
│ JSON validation │
|
||||
│ (analyzer.py) │
|
||||
├────────────────────────┤
|
||||
│ Parse: action, risk, │
|
||||
│ violations, summary │
|
||||
└───────────┬────────────┘
|
||||
│ DLPResult
|
||||
▼
|
||||
┌────────────────────────┐
|
||||
│ CLI Output │
|
||||
├────────────────────────┤
|
||||
│ - Write JSON to disk │
|
||||
│ - Rich summary table │
|
||||
└────────────────────────┘
|
||||
1
email_dlp/__init__.py
Normal file
1
email_dlp/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
# Email DLP - Data Loss Prevention for email content
|
||||
241
email_dlp/analyzer.py
Normal file
241
email_dlp/analyzer.py
Normal file
@ -0,0 +1,241 @@
|
||||
"""DLP analysis backends: remote LLM or deterministic local simulation."""
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
from typing import Any
|
||||
|
||||
import openai
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv()
|
||||
|
||||
from .converter import IMAGE_SENTINEL
|
||||
from .models import ActionClass, AttachmentResult, DLPResult, RiskLevel, ViolationType
|
||||
from .policy import format_policy_for_prompt
|
||||
from .simulator import simulate_analysis
|
||||
|
||||
OUTPUT_SCHEMA = {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"risk_level": {"type": "string", "enum": ["CRITICAL", "HIGH", "MEDIUM", "LOW"]},
|
||||
"risk_score": {"type": "integer", "minimum": 0, "maximum": 100},
|
||||
"violation_types": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"PII",
|
||||
"FINANCIAL_DATA",
|
||||
"SOURCE_CODE",
|
||||
"REGULATORY_DOCUMENT",
|
||||
"LEGAL_CONTRACT",
|
||||
"PAYROLL_RECORD",
|
||||
"CUSTOMER_LIST",
|
||||
"INTERNAL_MEMO",
|
||||
"NONE",
|
||||
],
|
||||
},
|
||||
},
|
||||
"action": {"type": "string", "enum": ["PASS", "ALERT", "BLOCK"]},
|
||||
"summary": {"type": "string"},
|
||||
"evidence": {"type": "array", "items": {"type": "string"}},
|
||||
},
|
||||
"required": ["risk_level", "risk_score", "violation_types", "action", "summary", "evidence"],
|
||||
}
|
||||
|
||||
SYSTEM_PROMPT_TEMPLATE = """\
|
||||
You are a Data Loss Prevention (DLP) analyst. Your task is to evaluate email content and attachments against the DLP policy below, then return a structured JSON decision.
|
||||
|
||||
## DLP Policy
|
||||
{policy_json}
|
||||
|
||||
## Output Schema
|
||||
Respond with valid JSON only (no markdown fences, no extra text) matching this schema:
|
||||
{schema_json}
|
||||
|
||||
## Critical Rules
|
||||
1. temperature=0: be deterministic and consistent.
|
||||
2. evidence: include direct quotes (verbatim excerpts) from the actual email or attachment content that justify your decision. Do not paraphrase.
|
||||
3. risk_score: assign 0-100. Use the full range — a customer CSV with 500 rows should score 95+, a casual internal memo scores 60-70.
|
||||
4. action: MUST match the threshold — BLOCK if risk_score>=80, ALERT if risk_score>=40, PASS otherwise.
|
||||
5. If no policy violations are found, set violation_types=["NONE"], risk_level="LOW", risk_score<40, action="PASS".
|
||||
"""
|
||||
|
||||
|
||||
def build_system_prompt() -> str:
|
||||
"""Build the system prompt used for DLP analysis."""
|
||||
return SYSTEM_PROMPT_TEMPLATE.format(
|
||||
policy_json=format_policy_for_prompt(),
|
||||
schema_json=json.dumps(OUTPUT_SCHEMA, indent=2),
|
||||
)
|
||||
|
||||
|
||||
def _build_user_content(
|
||||
subject: str,
|
||||
sender: str,
|
||||
recipient: str,
|
||||
date: str,
|
||||
body_text: str,
|
||||
attachment_texts: list[tuple[str, str]], # [(filename, text_or_sentinel)]
|
||||
) -> list[dict[str, Any]]:
|
||||
"""Build the user message content for the VLM.
|
||||
|
||||
Returns a multimodal content list (OpenAI vision format).
|
||||
Text attachments are embedded as text blocks; image attachments are
|
||||
inserted as image_url blocks so the VLM can see them directly.
|
||||
"""
|
||||
header_block = "\n".join([
|
||||
"## Email Headers",
|
||||
f"Subject: {subject}",
|
||||
f"From: {sender}",
|
||||
f"To: {recipient}",
|
||||
f"Date: {date}",
|
||||
"",
|
||||
"## Email Body",
|
||||
body_text or "(empty body)",
|
||||
])
|
||||
content: list[dict[str, Any]] = [{"type": "text", "text": header_block}]
|
||||
|
||||
for filename, text in attachment_texts:
|
||||
if text.startswith(IMAGE_SENTINEL):
|
||||
# IMAGE_SENTINEL format: "__IMAGE__:<mime>:<base64>"
|
||||
payload = text[len(IMAGE_SENTINEL):] # "<mime>:<base64>"
|
||||
mime, b64 = payload.split(":", 1)
|
||||
content.append({"type": "text", "text": f"\n## Attachment: {filename} (image)"})
|
||||
content.append({
|
||||
"type": "image_url",
|
||||
"image_url": {"url": f"data:{mime};base64,{b64}"},
|
||||
})
|
||||
else:
|
||||
content.append({
|
||||
"type": "text",
|
||||
"text": f"\n## Attachment: {filename}\n{text or '(no extractable text)'}",
|
||||
})
|
||||
|
||||
return content
|
||||
|
||||
|
||||
def _parse_llm_response(content: str) -> dict[str, Any]:
|
||||
"""Parse JSON from LLM response, handling markdown fences."""
|
||||
content = content.strip()
|
||||
|
||||
# Strip triple-backtick fences
|
||||
fence_match = re.search(r"```(?:json)?\s*([\s\S]*?)\s*```", content)
|
||||
if fence_match:
|
||||
content = fence_match.group(1)
|
||||
|
||||
return json.loads(content)
|
||||
|
||||
|
||||
def _map_action(action_str: str) -> ActionClass:
|
||||
mapping = {"PASS": ActionClass.PASS_, "ALERT": ActionClass.ALERT, "BLOCK": ActionClass.BLOCK}
|
||||
return mapping.get(action_str.upper(), ActionClass.ALERT)
|
||||
|
||||
|
||||
def analyze_email(
|
||||
email_file: str,
|
||||
subject: str,
|
||||
sender: str,
|
||||
recipient: str,
|
||||
date: str,
|
||||
body_text: str,
|
||||
attachment_texts: list[tuple[str, str]],
|
||||
attachment_results: list[AttachmentResult],
|
||||
processing_errors: list[str],
|
||||
endpoint: str = "http://localhost:8000/v1",
|
||||
model: str = "Qwen/Qwen3.5-35B-A3B",
|
||||
backend: str = "llm",
|
||||
) -> DLPResult:
|
||||
"""Analyze email content using an LLM or the deterministic simulator."""
|
||||
if backend == "simulated":
|
||||
return simulate_analysis(
|
||||
email_file=email_file,
|
||||
subject=subject,
|
||||
sender=sender,
|
||||
recipient=recipient,
|
||||
date=date,
|
||||
body_text=body_text,
|
||||
attachment_texts=attachment_texts,
|
||||
attachment_results=attachment_results,
|
||||
processing_errors=processing_errors,
|
||||
)
|
||||
|
||||
# Use environment variables as fallback if they exist
|
||||
final_endpoint = os.getenv("OPENAI_BASE_URL", endpoint)
|
||||
final_api_key = os.getenv("OPENAI_API_KEY", "not-needed")
|
||||
final_model = os.getenv("MODEL_NAME", model)
|
||||
|
||||
client = openai.OpenAI(base_url=final_endpoint, api_key=final_api_key)
|
||||
|
||||
system_prompt = build_system_prompt()
|
||||
|
||||
user_content = _build_user_content(
|
||||
subject=subject,
|
||||
sender=sender,
|
||||
recipient=recipient,
|
||||
date=date,
|
||||
body_text=body_text,
|
||||
attachment_texts=attachment_texts,
|
||||
)
|
||||
|
||||
response = client.chat.completions.create(
|
||||
model=final_model,
|
||||
messages=[
|
||||
{"role": "system", "content": system_prompt},
|
||||
{"role": "user", "content": user_content}, # list[dict] for multimodal
|
||||
],
|
||||
temperature=0.0,
|
||||
max_tokens=1024,
|
||||
extra_body={"chat_template_kwargs": {"enable_thinking": False}},
|
||||
)
|
||||
|
||||
raw_content = response.choices[0].message.content or ""
|
||||
|
||||
try:
|
||||
parsed = _parse_llm_response(raw_content)
|
||||
except json.JSONDecodeError as e:
|
||||
processing_errors.append(f"JSON parse error: {e}; raw={raw_content[:200]}")
|
||||
# Return a safe fallback
|
||||
return DLPResult(
|
||||
email_file=email_file,
|
||||
subject=subject,
|
||||
sender=sender,
|
||||
recipient=recipient,
|
||||
date=date,
|
||||
risk_level=RiskLevel.HIGH,
|
||||
risk_score=60,
|
||||
violation_types=[ViolationType.NONE],
|
||||
action=ActionClass.ALERT,
|
||||
summary="Analysis failed due to JSON parse error.",
|
||||
evidence=[],
|
||||
attachments=attachment_results,
|
||||
processing_errors=processing_errors,
|
||||
)
|
||||
|
||||
# Map string values to enums
|
||||
risk_level = RiskLevel(parsed.get("risk_level", "HIGH"))
|
||||
violation_types = [
|
||||
ViolationType(v) for v in parsed.get("violation_types", ["NONE"])
|
||||
if v in ViolationType.__members__
|
||||
]
|
||||
if not violation_types:
|
||||
violation_types = [ViolationType.NONE]
|
||||
|
||||
action = _map_action(parsed.get("action", "ALERT"))
|
||||
|
||||
return DLPResult(
|
||||
email_file=email_file,
|
||||
subject=subject,
|
||||
sender=sender,
|
||||
recipient=recipient,
|
||||
date=date,
|
||||
risk_level=risk_level,
|
||||
risk_score=int(parsed.get("risk_score", 60)),
|
||||
violation_types=violation_types,
|
||||
action=action,
|
||||
summary=parsed.get("summary", ""),
|
||||
evidence=parsed.get("evidence", []),
|
||||
attachments=attachment_results,
|
||||
processing_errors=processing_errors,
|
||||
)
|
||||
550
email_dlp/cli.py
Normal file
550
email_dlp/cli.py
Normal file
@ -0,0 +1,550 @@
|
||||
"""CLI entry point: preview and batch process .eml files through the DLP pipeline."""
|
||||
|
||||
import json
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
import typer
|
||||
from rich.console import Console
|
||||
from rich.progress import BarColumn, Progress, SpinnerColumn, TextColumn, TimeElapsedColumn
|
||||
from rich.table import Table
|
||||
|
||||
from .analyzer import _build_user_content, analyze_email, build_system_prompt
|
||||
from .converter import IMAGE_SENTINEL, convert_attachment
|
||||
from .models import ActionClass, AttachmentResult, DLPResult
|
||||
from .parser import parse_eml
|
||||
from .policy_reviewer import review_corpus
|
||||
from .simulator import simulate_analysis
|
||||
|
||||
app = typer.Typer(help="Email DLP — scan .eml files for data loss prevention policy violations.")
|
||||
console = Console()
|
||||
|
||||
ACTION_COLORS = {
|
||||
ActionClass.BLOCK: "bold red",
|
||||
ActionClass.ALERT: "bold yellow",
|
||||
ActionClass.PASS_: "bold green",
|
||||
}
|
||||
|
||||
RISK_COLORS = {
|
||||
"CRITICAL": "bold red",
|
||||
"HIGH": "red",
|
||||
"MEDIUM": "yellow",
|
||||
"LOW": "green",
|
||||
}
|
||||
|
||||
|
||||
def _process_single_email(
|
||||
eml_path: Path,
|
||||
endpoint: str,
|
||||
model: str,
|
||||
backend: str = "llm",
|
||||
) -> DLPResult:
|
||||
"""Parse, convert, and analyze one .eml file."""
|
||||
processing_errors: list[str] = []
|
||||
|
||||
# 1. Parse MIME
|
||||
parsed = parse_eml(eml_path)
|
||||
|
||||
try:
|
||||
# 2. Convert attachments
|
||||
attachment_texts: list[tuple[str, str]] = []
|
||||
attachment_results: list[AttachmentResult] = []
|
||||
|
||||
for att in parsed.attachments:
|
||||
entries = convert_attachment(att.path, att.filename)
|
||||
for display_name, text, status in entries:
|
||||
if "truncated" in status:
|
||||
processing_errors.append(
|
||||
f"'{display_name}' truncated to 20000 chars"
|
||||
)
|
||||
attachment_texts.append((display_name, text))
|
||||
attachment_results.append(
|
||||
AttachmentResult(
|
||||
filename=display_name,
|
||||
content_type=att.content_type,
|
||||
extracted_text_chars=0 if text.startswith(IMAGE_SENTINEL) else len(text),
|
||||
conversion_status=status.split("|")[0],
|
||||
)
|
||||
)
|
||||
|
||||
# 3. Analyze with LLM
|
||||
result = analyze_email(
|
||||
email_file=eml_path.name,
|
||||
subject=parsed.subject,
|
||||
sender=parsed.sender,
|
||||
recipient=parsed.recipient,
|
||||
date=parsed.date,
|
||||
body_text=parsed.body_text,
|
||||
attachment_texts=attachment_texts,
|
||||
attachment_results=attachment_results,
|
||||
processing_errors=processing_errors,
|
||||
endpoint=endpoint,
|
||||
model=model,
|
||||
backend=backend,
|
||||
)
|
||||
finally:
|
||||
parsed.cleanup()
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def _simulate_single_email(eml_path: Path) -> DLPResult:
|
||||
"""Parse, convert, and simulate one .eml file without an LLM."""
|
||||
processing_errors: list[str] = []
|
||||
parsed = parse_eml(eml_path)
|
||||
|
||||
try:
|
||||
attachment_texts: list[tuple[str, str]] = []
|
||||
attachment_results: list[AttachmentResult] = []
|
||||
|
||||
for att in parsed.attachments:
|
||||
entries = convert_attachment(att.path, att.filename)
|
||||
for display_name, text, status in entries:
|
||||
if "truncated" in status:
|
||||
processing_errors.append(
|
||||
f"'{display_name}' truncated to 20000 chars"
|
||||
)
|
||||
attachment_texts.append((display_name, text))
|
||||
attachment_results.append(
|
||||
AttachmentResult(
|
||||
filename=display_name,
|
||||
content_type=att.content_type,
|
||||
extracted_text_chars=0 if text.startswith(IMAGE_SENTINEL) else len(text),
|
||||
conversion_status=status.split("|")[0],
|
||||
)
|
||||
)
|
||||
|
||||
result = simulate_analysis(
|
||||
email_file=eml_path.name,
|
||||
subject=parsed.subject,
|
||||
sender=parsed.sender,
|
||||
recipient=parsed.recipient,
|
||||
date=parsed.date,
|
||||
body_text=parsed.body_text,
|
||||
attachment_texts=attachment_texts,
|
||||
attachment_results=attachment_results,
|
||||
processing_errors=processing_errors,
|
||||
)
|
||||
finally:
|
||||
parsed.cleanup()
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def _preview_single_email(
|
||||
eml_path: Path,
|
||||
include_system_prompt: bool = True,
|
||||
include_full_prompt: bool = False,
|
||||
) -> dict:
|
||||
"""Parse and convert one .eml file without calling the LLM."""
|
||||
parsed = parse_eml(eml_path)
|
||||
|
||||
try:
|
||||
attachments_preview: list[dict[str, object]] = []
|
||||
attachment_texts: list[tuple[str, str]] = []
|
||||
processing_errors: list[str] = []
|
||||
|
||||
for att in parsed.attachments:
|
||||
entries = convert_attachment(att.path, att.filename)
|
||||
for display_name, text, status in entries:
|
||||
is_image = text.startswith(IMAGE_SENTINEL)
|
||||
if "truncated" in status:
|
||||
processing_errors.append(
|
||||
f"'{display_name}' truncated to 20000 chars"
|
||||
)
|
||||
|
||||
attachment_texts.append((display_name, text))
|
||||
attachments_preview.append(
|
||||
{
|
||||
"filename": display_name,
|
||||
"content_type": att.content_type,
|
||||
"conversion_status": status,
|
||||
"is_image": is_image,
|
||||
"extracted_text_chars": 0 if is_image else len(text),
|
||||
"text_preview": (
|
||||
None
|
||||
if is_image
|
||||
else text[:500]
|
||||
),
|
||||
"image_data_url_preview": (
|
||||
None
|
||||
if not is_image
|
||||
else text[:120] + "..."
|
||||
if len(text) > 120
|
||||
else text
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
llm_user_content = _build_user_content(
|
||||
subject=parsed.subject,
|
||||
sender=parsed.sender,
|
||||
recipient=parsed.recipient,
|
||||
date=parsed.date,
|
||||
body_text=parsed.body_text,
|
||||
attachment_texts=attachment_texts,
|
||||
)
|
||||
|
||||
llm_user_content_preview: list[dict[str, object]] = []
|
||||
for block in llm_user_content:
|
||||
if block["type"] == "text":
|
||||
llm_user_content_preview.append(
|
||||
{
|
||||
"type": "text",
|
||||
"text_preview": str(block["text"])[:1000],
|
||||
"text_chars": len(str(block["text"])),
|
||||
}
|
||||
)
|
||||
else:
|
||||
url = str(block["image_url"]["url"])
|
||||
llm_user_content_preview.append(
|
||||
{
|
||||
"type": "image_url",
|
||||
"url_preview": url[:120] + ("..." if len(url) > 120 else ""),
|
||||
"url_chars": len(url),
|
||||
}
|
||||
)
|
||||
|
||||
preview_result = {
|
||||
"email_file": eml_path.name,
|
||||
"subject": parsed.subject,
|
||||
"sender": parsed.sender,
|
||||
"recipient": parsed.recipient,
|
||||
"date": parsed.date,
|
||||
"body_text_chars": len(parsed.body_text),
|
||||
"body_text_preview": parsed.body_text[:1000],
|
||||
"attachment_count": len(parsed.attachments),
|
||||
"attachments": attachments_preview,
|
||||
"processing_errors": processing_errors,
|
||||
"llm_user_content_preview": llm_user_content_preview,
|
||||
}
|
||||
|
||||
if include_system_prompt:
|
||||
system_prompt = build_system_prompt()
|
||||
preview_result["llm_system_prompt_preview"] = {
|
||||
"text_preview": system_prompt[:2000],
|
||||
"text_chars": len(system_prompt),
|
||||
}
|
||||
if include_full_prompt:
|
||||
preview_result["llm_system_prompt"] = system_prompt
|
||||
|
||||
if include_full_prompt:
|
||||
preview_result["llm_user_content"] = llm_user_content
|
||||
|
||||
return preview_result
|
||||
finally:
|
||||
parsed.cleanup()
|
||||
|
||||
|
||||
@app.command()
|
||||
def analyze(
|
||||
input_dir: Path = typer.Option(
|
||||
Path("data"),
|
||||
"--input",
|
||||
"-i",
|
||||
help="Directory containing .eml files",
|
||||
),
|
||||
output_dir: Optional[Path] = typer.Option(
|
||||
None,
|
||||
"--output",
|
||||
"-o",
|
||||
help="Directory to write JSON results (defaults to output/analyze-TIMESTAMP)",
|
||||
),
|
||||
endpoint: str = typer.Option(
|
||||
"http://localhost:8000/v1",
|
||||
"--endpoint",
|
||||
help="vLLM OpenAI-compatible endpoint",
|
||||
),
|
||||
model: str = typer.Option(
|
||||
"Qwen/Qwen3.5-35B-A3B",
|
||||
"--model",
|
||||
help="Model name to use for analysis",
|
||||
),
|
||||
backend: str = typer.Option(
|
||||
"llm",
|
||||
"--backend",
|
||||
help="Analysis backend: 'llm' for API calls, 'simulated' for local deterministic analysis",
|
||||
),
|
||||
summary: bool = typer.Option(
|
||||
False,
|
||||
"--summary",
|
||||
"-s",
|
||||
help="Print a summary table after processing",
|
||||
),
|
||||
) -> None:
|
||||
"""Batch analyze all .eml files in INPUT_DIR and write JSON results to OUTPUT_DIR."""
|
||||
if output_dir is None:
|
||||
timestamp = datetime.now().strftime("%Y%m%d-%H%M%S")
|
||||
output_dir = Path("output") / f"analyze-{timestamp}"
|
||||
|
||||
eml_files = sorted(input_dir.glob("*.eml"))
|
||||
if not eml_files:
|
||||
console.print(f"[red]No .eml files found in {input_dir}[/red]")
|
||||
raise typer.Exit(1)
|
||||
|
||||
output_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
results: list[DLPResult] = []
|
||||
|
||||
with Progress(
|
||||
SpinnerColumn(),
|
||||
TextColumn("[progress.description]{task.description}"),
|
||||
BarColumn(),
|
||||
TextColumn("{task.completed}/{task.total}"),
|
||||
TimeElapsedColumn(),
|
||||
console=console,
|
||||
) as progress:
|
||||
task = progress.add_task("Analyzing emails...", total=len(eml_files))
|
||||
|
||||
for eml_path in eml_files:
|
||||
progress.update(task, description=f"[cyan]{eml_path.name[:50]}[/cyan]")
|
||||
try:
|
||||
result = _process_single_email(eml_path, endpoint, model, backend=backend)
|
||||
except Exception as e:
|
||||
console.print(f"[red]Error processing {eml_path.name}: {e}[/red]")
|
||||
progress.advance(task)
|
||||
continue
|
||||
|
||||
# Write individual JSON result
|
||||
out_file = output_dir / (eml_path.stem + ".json")
|
||||
out_file.write_text(result.model_dump_json(indent=2))
|
||||
results.append(result)
|
||||
progress.advance(task)
|
||||
|
||||
# Write batch summary
|
||||
batch_summary = {
|
||||
"total": len(results),
|
||||
"by_action": {
|
||||
"BLOCK": sum(1 for r in results if r.action == ActionClass.BLOCK),
|
||||
"ALERT": sum(1 for r in results if r.action == ActionClass.ALERT),
|
||||
"PASS": sum(1 for r in results if r.action == ActionClass.PASS_),
|
||||
},
|
||||
"by_risk": {
|
||||
level: sum(1 for r in results if r.risk_level.value == level)
|
||||
for level in ["CRITICAL", "HIGH", "MEDIUM", "LOW"]
|
||||
},
|
||||
"emails": [
|
||||
{
|
||||
"file": r.email_file,
|
||||
"subject": r.subject,
|
||||
"risk_level": r.risk_level.value,
|
||||
"risk_score": r.risk_score,
|
||||
"action": r.action.value,
|
||||
"violation_types": [v.value for v in r.violation_types],
|
||||
}
|
||||
for r in results
|
||||
],
|
||||
}
|
||||
(output_dir / "batch_summary.json").write_text(
|
||||
json.dumps(batch_summary, indent=2)
|
||||
)
|
||||
|
||||
console.print(f"\n[bold green]Done![/bold green] Processed {len(results)}/{len(eml_files)} emails.")
|
||||
console.print(f"Results written to: [cyan]{output_dir}/[/cyan]")
|
||||
|
||||
if summary and results:
|
||||
_print_summary_table(results)
|
||||
|
||||
|
||||
@app.command()
|
||||
def preview(
|
||||
input_dir: Path = typer.Option(
|
||||
Path("data"),
|
||||
"--input",
|
||||
"-i",
|
||||
help="Directory containing .eml files",
|
||||
),
|
||||
output_dir: Optional[Path] = typer.Option(
|
||||
None,
|
||||
"--output",
|
||||
"-o",
|
||||
help="Optional directory to write preview JSON files",
|
||||
),
|
||||
print_json: bool = typer.Option(
|
||||
False,
|
||||
"--print-json",
|
||||
help="Print preview JSON to stdout",
|
||||
),
|
||||
include_system_prompt: bool = typer.Option(
|
||||
True,
|
||||
"--include-system-prompt/--no-system-prompt",
|
||||
help="Include the analyzer system prompt built from policy.py",
|
||||
),
|
||||
include_full_prompt: bool = typer.Option(
|
||||
False,
|
||||
"--include-full-prompt",
|
||||
help="Include the full system prompt and full user content in JSON output",
|
||||
),
|
||||
) -> None:
|
||||
"""Preview parsed email and converted attachment content before LLM analysis."""
|
||||
eml_files = sorted(input_dir.glob("*.eml"))
|
||||
if not eml_files:
|
||||
console.print(f"[red]No .eml files found in {input_dir}[/red]")
|
||||
raise typer.Exit(1)
|
||||
|
||||
if output_dir is not None:
|
||||
output_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
previews: list[dict] = []
|
||||
|
||||
for eml_path in eml_files:
|
||||
try:
|
||||
preview_result = _preview_single_email(
|
||||
eml_path,
|
||||
include_system_prompt=include_system_prompt,
|
||||
include_full_prompt=include_full_prompt,
|
||||
)
|
||||
except Exception as e:
|
||||
console.print(f"[red]Error previewing {eml_path.name}: {e}[/red]")
|
||||
continue
|
||||
|
||||
previews.append(preview_result)
|
||||
|
||||
if output_dir is not None:
|
||||
out_file = output_dir / f"{eml_path.stem}.preview.json"
|
||||
out_file.write_text(json.dumps(preview_result, indent=2))
|
||||
|
||||
if output_dir is not None:
|
||||
batch_file = output_dir / "batch_preview.json"
|
||||
batch_file.write_text(json.dumps(previews, indent=2))
|
||||
console.print(f"[green]Preview JSON written to[/green] [cyan]{output_dir}[/cyan]")
|
||||
|
||||
if print_json:
|
||||
console.print_json(json.dumps(previews, indent=2))
|
||||
else:
|
||||
table = Table(title="Email Preview Results", show_lines=True)
|
||||
table.add_column("File", style="dim", max_width=45)
|
||||
table.add_column("Body Chars", justify="right")
|
||||
table.add_column("Attachments", justify="right")
|
||||
table.add_column("Errors", justify="right")
|
||||
|
||||
for preview_result in previews:
|
||||
table.add_row(
|
||||
str(preview_result["email_file"]),
|
||||
str(preview_result["body_text_chars"]),
|
||||
str(preview_result["attachment_count"]),
|
||||
str(len(preview_result["processing_errors"])),
|
||||
)
|
||||
|
||||
console.print(table)
|
||||
|
||||
|
||||
@app.command()
|
||||
def simulate(
|
||||
input_dir: Path = typer.Option(
|
||||
Path("data"),
|
||||
"--input",
|
||||
"-i",
|
||||
help="Directory containing .eml files",
|
||||
),
|
||||
output_dir: Optional[Path] = typer.Option(
|
||||
None,
|
||||
"--output",
|
||||
"-o",
|
||||
help="Directory to write simulated JSON results (defaults to output/simulated-TIMESTAMP)",
|
||||
),
|
||||
summary: bool = typer.Option(
|
||||
True,
|
||||
"--summary/--no-summary",
|
||||
help="Print a summary table after processing",
|
||||
),
|
||||
) -> None:
|
||||
"""Batch simulate DLP analysis locally without calling an LLM."""
|
||||
if output_dir is None:
|
||||
timestamp = datetime.now().strftime("%Y%m%d-%H%M%S")
|
||||
output_dir = Path("output") / f"simulated-{timestamp}"
|
||||
|
||||
eml_files = sorted(input_dir.glob("*.eml"))
|
||||
if not eml_files:
|
||||
console.print(f"[red]No .eml files found in {input_dir}[/red]")
|
||||
raise typer.Exit(1)
|
||||
|
||||
output_dir.mkdir(parents=True, exist_ok=True)
|
||||
results: list[DLPResult] = []
|
||||
|
||||
with Progress(
|
||||
SpinnerColumn(),
|
||||
TextColumn("[progress.description]{task.description}"),
|
||||
BarColumn(),
|
||||
TextColumn("{task.completed}/{task.total}"),
|
||||
TimeElapsedColumn(),
|
||||
console=console,
|
||||
) as progress:
|
||||
task = progress.add_task("Simulating email analysis...", total=len(eml_files))
|
||||
|
||||
for eml_path in eml_files:
|
||||
progress.update(task, description=f"[cyan]{eml_path.name[:50]}[/cyan]")
|
||||
try:
|
||||
result = _simulate_single_email(eml_path)
|
||||
except Exception as e:
|
||||
console.print(f"[red]Error processing {eml_path.name}: {e}[/red]")
|
||||
progress.advance(task)
|
||||
continue
|
||||
|
||||
out_file = output_dir / (eml_path.stem + ".json")
|
||||
out_file.write_text(result.model_dump_json(indent=2))
|
||||
results.append(result)
|
||||
progress.advance(task)
|
||||
|
||||
batch_summary = {
|
||||
"total": len(results),
|
||||
"generator": "local-simulator",
|
||||
"model_label": "gpt-5.4-simulated",
|
||||
"by_action": {
|
||||
"BLOCK": sum(1 for r in results if r.action == ActionClass.BLOCK),
|
||||
"ALERT": sum(1 for r in results if r.action == ActionClass.ALERT),
|
||||
"PASS": sum(1 for r in results if r.action == ActionClass.PASS_),
|
||||
},
|
||||
"by_risk": {
|
||||
level: sum(1 for r in results if r.risk_level.value == level)
|
||||
for level in ["CRITICAL", "HIGH", "MEDIUM", "LOW"]
|
||||
},
|
||||
"emails": [
|
||||
{
|
||||
"file": r.email_file,
|
||||
"subject": r.subject,
|
||||
"risk_level": r.risk_level.value,
|
||||
"risk_score": r.risk_score,
|
||||
"action": r.action.value,
|
||||
"violation_types": [v.value for v in r.violation_types],
|
||||
}
|
||||
for r in results
|
||||
],
|
||||
}
|
||||
(output_dir / "batch_summary.json").write_text(
|
||||
json.dumps(batch_summary, indent=2)
|
||||
)
|
||||
|
||||
console.print(
|
||||
f"\n[bold green]Done![/bold green] Simulated {len(results)}/{len(eml_files)} emails."
|
||||
)
|
||||
console.print(f"Results written to: [cyan]{output_dir}/[/cyan]")
|
||||
|
||||
if summary and results:
|
||||
_print_summary_table(results)
|
||||
|
||||
|
||||
def _print_summary_table(results: list[DLPResult]) -> None:
|
||||
"""Print a rich summary table to the console."""
|
||||
table = Table(title="Email DLP Analysis Results", show_lines=True)
|
||||
table.add_column("File", style="dim", max_width=45)
|
||||
table.add_column("Risk Level", justify="center")
|
||||
table.add_column("Score", justify="center")
|
||||
table.add_column("Action", justify="center")
|
||||
table.add_column("Violations", max_width=40)
|
||||
|
||||
for r in results:
|
||||
risk_color = RISK_COLORS.get(r.risk_level.value, "white")
|
||||
action_color = ACTION_COLORS.get(r.action, "white")
|
||||
violations = ", ".join(v.value for v in r.violation_types)
|
||||
|
||||
table.add_row(
|
||||
r.email_file,
|
||||
f"[{risk_color}]{r.risk_level.value}[/{risk_color}]",
|
||||
str(r.risk_score),
|
||||
f"[{action_color}]{r.action.value}[/{action_color}]",
|
||||
violations,
|
||||
)
|
||||
|
||||
console.print(table)
|
||||
238
email_dlp/converter.py
Normal file
238
email_dlp/converter.py
Normal file
@ -0,0 +1,238 @@
|
||||
"""Attachment → markdown text conversion routing."""
|
||||
|
||||
import base64
|
||||
import tempfile
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
from markitdown import MarkItDown
|
||||
|
||||
MAX_TEXT_CHARS = 20_000
|
||||
|
||||
# Sentinel prefix used to pass image data through the (text, status) interface.
|
||||
# Format: IMAGE_SENTINEL + "<mime_type>:<base64_data>"
|
||||
IMAGE_SENTINEL = "__IMAGE__:"
|
||||
|
||||
_IMAGE_MIME = {
|
||||
".jpg": "image/jpeg", ".jpeg": "image/jpeg",
|
||||
".png": "image/png", ".gif": "image/gif",
|
||||
".bmp": "image/bmp", ".tiff": "image/tiff", ".webp": "image/webp",
|
||||
".img": "image/png", # fallback for generated inline names
|
||||
}
|
||||
|
||||
|
||||
def _convert_single_file(filepath: Path) -> tuple[str, str]:
|
||||
"""Convert a single file to text. Returns (text, status).
|
||||
|
||||
For image files, text is IMAGE_SENTINEL + "<mime>:<base64>" and
|
||||
status is "ok:image". Callers must check for the sentinel.
|
||||
"""
|
||||
suffix = filepath.suffix.lower()
|
||||
|
||||
# Image — return base64 sentinel for VLM consumption
|
||||
if suffix in _IMAGE_MIME:
|
||||
mime = _IMAGE_MIME[suffix]
|
||||
b64 = base64.b64encode(filepath.read_bytes()).decode()
|
||||
return IMAGE_SENTINEL + f"{mime}:{b64}", "ok:image"
|
||||
|
||||
known_binary_exts = {
|
||||
".py", ".js", ".ts", ".java", ".c", ".cpp", ".h", ".cs",
|
||||
".go", ".rb", ".rs", ".sh", ".txt", ".md", ".sql", ".yaml", ".yml",
|
||||
".json", ".xml", ".html", ".htm", ".css",
|
||||
}
|
||||
|
||||
if suffix in known_binary_exts:
|
||||
# Plain text fallback — read directly
|
||||
try:
|
||||
text = filepath.read_text(errors="replace")
|
||||
return text, "ok"
|
||||
except Exception as e:
|
||||
return "", f"failed: {e}"
|
||||
|
||||
# Use markitdown for PDF, DOCX, XLSX, CSV, etc.
|
||||
try:
|
||||
md = MarkItDown()
|
||||
result = md.convert(str(filepath))
|
||||
return result.text_content or "", "ok"
|
||||
except Exception as e:
|
||||
# Fallback to plain-text read for unknown types
|
||||
try:
|
||||
text = filepath.read_text(errors="replace")
|
||||
return text, f"fallback: {e}"
|
||||
except Exception as e2:
|
||||
return "", f"failed: {e2}"
|
||||
|
||||
|
||||
_OFFICE_MEDIA_DIRS = {
|
||||
".docx": "word/media/",
|
||||
".pptx": "ppt/media/",
|
||||
".xlsx": "xl/media/",
|
||||
}
|
||||
|
||||
_IMAGE_EXTS = set(_IMAGE_MIME.keys())
|
||||
|
||||
|
||||
def _extract_pdf_images(
|
||||
filepath: Path, filename: str
|
||||
) -> list[tuple[str, str, str]]:
|
||||
"""Extract embedded images from a PDF using PyMuPDF.
|
||||
|
||||
Returns list of (display_name, IMAGE_SENTINEL+..., "ok:image").
|
||||
Returns empty list if fitz is not installed or no images found.
|
||||
"""
|
||||
try:
|
||||
import fitz # PyMuPDF
|
||||
except ImportError:
|
||||
return []
|
||||
|
||||
results: list[tuple[str, str, str]] = []
|
||||
try:
|
||||
doc = fitz.open(str(filepath))
|
||||
img_index = 0
|
||||
for page in doc:
|
||||
for img in page.get_images():
|
||||
xref = img[0]
|
||||
img_data = doc.extract_image(xref)
|
||||
ext = img_data.get("ext", "png")
|
||||
mime = _IMAGE_MIME.get(f".{ext}", f"image/{ext}")
|
||||
b64 = base64.b64encode(img_data["image"]).decode()
|
||||
display_name = f"{filename}/image_{img_index}.{ext}"
|
||||
results.append((display_name, IMAGE_SENTINEL + f"{mime}:{b64}", "ok:image"))
|
||||
img_index += 1
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def _extract_office_images(
|
||||
filepath: Path, filename: str
|
||||
) -> list[tuple[str, str, str]]:
|
||||
"""Extract embedded images from a DOCX/PPTX/XLSX using zipfile.
|
||||
|
||||
Returns list of (display_name, IMAGE_SENTINEL+..., "ok:image").
|
||||
Returns empty list if the file is not a valid ZIP or has no images.
|
||||
"""
|
||||
suffix = Path(filename).suffix.lower()
|
||||
media_dir = _OFFICE_MEDIA_DIRS.get(suffix)
|
||||
if not media_dir:
|
||||
return []
|
||||
|
||||
results: list[tuple[str, str, str]] = []
|
||||
try:
|
||||
with zipfile.ZipFile(str(filepath), "r") as zf:
|
||||
for name in sorted(zf.namelist()):
|
||||
if not name.startswith(media_dir):
|
||||
continue
|
||||
member_suffix = Path(name).suffix.lower()
|
||||
if member_suffix not in _IMAGE_EXTS:
|
||||
continue
|
||||
mime = _IMAGE_MIME[member_suffix]
|
||||
b64 = base64.b64encode(zf.read(name)).decode()
|
||||
display_name = f"{filename}/{Path(name).name}"
|
||||
results.append((display_name, IMAGE_SENTINEL + f"{mime}:{b64}", "ok:image"))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def _convert_7z(
|
||||
filepath: Path, archive_name: str
|
||||
) -> list[tuple[str, str, str]]:
|
||||
"""Extract a .7z archive and convert each member.
|
||||
|
||||
Returns list of (display_name, text_or_sentinel, status), one entry per member.
|
||||
display_name uses "archive.7z/member.ext" format.
|
||||
"""
|
||||
try:
|
||||
import py7zr
|
||||
except ImportError:
|
||||
return [(archive_name, "", "failed: py7zr not installed")]
|
||||
|
||||
results: list[tuple[str, str, str]] = []
|
||||
with tempfile.TemporaryDirectory(prefix="email_dlp_7z_") as tmpdir:
|
||||
tmp = Path(tmpdir)
|
||||
try:
|
||||
with py7zr.SevenZipFile(str(filepath), mode="r") as archive:
|
||||
archive.extractall(path=str(tmp))
|
||||
except Exception as e:
|
||||
return [(archive_name, "", f"failed: 7z extraction error: {e}")]
|
||||
|
||||
for member_path in sorted(tmp.rglob("*")):
|
||||
if not member_path.is_file():
|
||||
continue
|
||||
display_name = f"{archive_name}/{member_path.name}"
|
||||
text, status = _convert_single_file(member_path)
|
||||
if not text.startswith(IMAGE_SENTINEL) and len(text) > MAX_TEXT_CHARS:
|
||||
text = text[:MAX_TEXT_CHARS]
|
||||
status = f"{status}|truncated_at_{MAX_TEXT_CHARS}"
|
||||
results.append((display_name, text, status))
|
||||
|
||||
return results if results else [(archive_name, "", "skipped")]
|
||||
|
||||
|
||||
def _convert_zip(
|
||||
filepath: Path, archive_name: str
|
||||
) -> list[tuple[str, str, str]]:
|
||||
"""Extract a .zip archive and convert each member.
|
||||
|
||||
Returns list of (display_name, text_or_sentinel, status), one entry per member.
|
||||
display_name uses "archive.zip/member.ext" format.
|
||||
"""
|
||||
import zipfile
|
||||
|
||||
results: list[tuple[str, str, str]] = []
|
||||
with tempfile.TemporaryDirectory(prefix="email_dlp_zip_") as tmpdir:
|
||||
tmp = Path(tmpdir)
|
||||
try:
|
||||
with zipfile.ZipFile(str(filepath), mode="r") as archive:
|
||||
archive.extractall(path=str(tmp))
|
||||
except Exception as e:
|
||||
return [(archive_name, "", f"failed: zip extraction error: {e}")]
|
||||
|
||||
for member_path in sorted(tmp.rglob("*")):
|
||||
if not member_path.is_file():
|
||||
continue
|
||||
display_name = f"{archive_name}/{member_path.name}"
|
||||
text, status = _convert_single_file(member_path)
|
||||
if not text.startswith(IMAGE_SENTINEL) and len(text) > MAX_TEXT_CHARS:
|
||||
text = text[:MAX_TEXT_CHARS]
|
||||
status = f"{status}|truncated_at_{MAX_TEXT_CHARS}"
|
||||
results.append((display_name, text, status))
|
||||
|
||||
return results if results else [(archive_name, "", "skipped")]
|
||||
|
||||
|
||||
def convert_attachment(
|
||||
filepath: Path, filename: str
|
||||
) -> list[tuple[str, str, str]]:
|
||||
"""Convert an attachment file for LLM analysis.
|
||||
|
||||
Returns list of (display_name, text_or_sentinel, status).
|
||||
- Non-archive files: single-element list.
|
||||
- .7z archives: one element per member file inside the archive.
|
||||
|
||||
text_or_sentinel is either plain text or IMAGE_SENTINEL + "<mime>:<base64>"
|
||||
for image files. Text is truncated to MAX_TEXT_CHARS (images are not truncated).
|
||||
"""
|
||||
suffix = Path(filename).suffix.lower()
|
||||
|
||||
if suffix == ".7z":
|
||||
return _convert_7z(filepath, filename)
|
||||
elif suffix == ".zip":
|
||||
return _convert_zip(filepath, filename)
|
||||
|
||||
text, status = _convert_single_file(filepath)
|
||||
if not text.startswith(IMAGE_SENTINEL) and len(text) > MAX_TEXT_CHARS:
|
||||
text = text[:MAX_TEXT_CHARS]
|
||||
status = f"{status}|truncated_at_{MAX_TEXT_CHARS}"
|
||||
results = [(filename, text, status)]
|
||||
|
||||
# For PDF and Office files, also extract embedded images
|
||||
if suffix == ".pdf":
|
||||
results.extend(_extract_pdf_images(filepath, filename))
|
||||
elif suffix in _OFFICE_MEDIA_DIRS:
|
||||
results.extend(_extract_office_images(filepath, filename))
|
||||
|
||||
return results
|
||||
52
email_dlp/models.py
Normal file
52
email_dlp/models.py
Normal file
@ -0,0 +1,52 @@
|
||||
"""Pydantic output schema for DLP analysis results."""
|
||||
|
||||
from enum import Enum
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class RiskLevel(str, Enum):
|
||||
CRITICAL = "CRITICAL"
|
||||
HIGH = "HIGH"
|
||||
MEDIUM = "MEDIUM"
|
||||
LOW = "LOW"
|
||||
|
||||
|
||||
class ViolationType(str, Enum):
|
||||
PII = "PII"
|
||||
FINANCIAL_DATA = "FINANCIAL_DATA"
|
||||
SOURCE_CODE = "SOURCE_CODE"
|
||||
REGULATORY_DOCUMENT = "REGULATORY_DOCUMENT"
|
||||
LEGAL_CONTRACT = "LEGAL_CONTRACT"
|
||||
PAYROLL_RECORD = "PAYROLL_RECORD"
|
||||
CUSTOMER_LIST = "CUSTOMER_LIST"
|
||||
INTERNAL_MEMO = "INTERNAL_MEMO"
|
||||
NONE = "NONE"
|
||||
|
||||
|
||||
class ActionClass(str, Enum):
|
||||
PASS_ = "PASS"
|
||||
ALERT = "ALERT"
|
||||
BLOCK = "BLOCK"
|
||||
|
||||
|
||||
class AttachmentResult(BaseModel):
|
||||
filename: str
|
||||
content_type: str
|
||||
extracted_text_chars: int = 0
|
||||
conversion_status: str = "ok" # "ok" | "failed" | "skipped"
|
||||
|
||||
|
||||
class DLPResult(BaseModel):
|
||||
email_file: str
|
||||
subject: str
|
||||
sender: str
|
||||
recipient: str
|
||||
date: str
|
||||
risk_level: RiskLevel
|
||||
risk_score: int = Field(ge=0, le=100)
|
||||
violation_types: list[ViolationType]
|
||||
action: ActionClass
|
||||
summary: str
|
||||
evidence: list[str]
|
||||
attachments: list[AttachmentResult]
|
||||
processing_errors: list[str] = Field(default_factory=list)
|
||||
199
email_dlp/parser.py
Normal file
199
email_dlp/parser.py
Normal file
@ -0,0 +1,199 @@
|
||||
"""MIME email parsing: extract headers, body text, and attachments."""
|
||||
|
||||
import email
|
||||
import email.policy
|
||||
import tempfile
|
||||
from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
|
||||
from bs4 import BeautifulSoup
|
||||
|
||||
|
||||
@dataclass
|
||||
class ParsedAttachment:
|
||||
filename: str
|
||||
path: Path
|
||||
content_type: str
|
||||
|
||||
|
||||
@dataclass
|
||||
class ParsedEmail:
|
||||
subject: str
|
||||
sender: str
|
||||
recipient: str
|
||||
date: str
|
||||
body_text: str
|
||||
attachments: list[ParsedAttachment] = field(default_factory=list)
|
||||
# tempdir must be kept alive by the caller
|
||||
_tempdir: tempfile.TemporaryDirectory | None = field(default=None, repr=False)
|
||||
|
||||
def cleanup(self) -> None:
|
||||
if self._tempdir is not None:
|
||||
self._tempdir.cleanup()
|
||||
self._tempdir = None
|
||||
|
||||
|
||||
def _decode_header_value(value: str | None) -> str:
|
||||
if value is None:
|
||||
return ""
|
||||
# Decode RFC2047 encoded words (e.g. =?Windows-1252?Q?...?=)
|
||||
decoded_parts = email.header.decode_header(str(value))
|
||||
result = ""
|
||||
for chunk, charset in decoded_parts:
|
||||
if isinstance(chunk, bytes):
|
||||
result += chunk.decode(charset or "utf-8", errors="replace")
|
||||
else:
|
||||
result += chunk
|
||||
return result.strip()
|
||||
|
||||
|
||||
def _extract_body(msg: email.message.Message) -> str:
|
||||
"""Walk MIME parts and extract the best plain-text body."""
|
||||
plain_parts: list[str] = []
|
||||
html_parts: list[str] = []
|
||||
|
||||
if msg.is_multipart():
|
||||
for part in msg.walk():
|
||||
ct = part.get_content_type()
|
||||
disposition = str(part.get("Content-Disposition", ""))
|
||||
# Skip attachments
|
||||
if "attachment" in disposition:
|
||||
continue
|
||||
if ct == "text/plain":
|
||||
payload = part.get_payload(decode=True)
|
||||
if payload:
|
||||
charset = part.get_content_charset() or "utf-8"
|
||||
plain_parts.append(payload.decode(charset, errors="replace"))
|
||||
elif ct == "text/html":
|
||||
payload = part.get_payload(decode=True)
|
||||
if payload:
|
||||
charset = part.get_content_charset() or "utf-8"
|
||||
html_parts.append(payload.decode(charset, errors="replace"))
|
||||
else:
|
||||
ct = msg.get_content_type()
|
||||
payload = msg.get_payload(decode=True)
|
||||
if payload:
|
||||
charset = msg.get_content_charset() or "utf-8"
|
||||
text = payload.decode(charset, errors="replace")
|
||||
if ct == "text/plain":
|
||||
plain_parts.append(text)
|
||||
elif ct == "text/html":
|
||||
html_parts.append(text)
|
||||
|
||||
if plain_parts:
|
||||
return "\n\n".join(plain_parts).strip()
|
||||
|
||||
# Fall back to HTML → plain text via BeautifulSoup
|
||||
if html_parts:
|
||||
combined_html = "\n".join(html_parts)
|
||||
soup = BeautifulSoup(combined_html, "html.parser")
|
||||
return soup.get_text(separator="\n").strip()
|
||||
|
||||
return ""
|
||||
|
||||
|
||||
_IMAGE_CONTENT_TYPES = {
|
||||
"image/jpeg", "image/png", "image/gif",
|
||||
"image/bmp", "image/tiff", "image/webp",
|
||||
}
|
||||
_IMAGE_EXTS = {
|
||||
"image/jpeg": ".jpg", "image/png": ".png", "image/gif": ".gif",
|
||||
"image/bmp": ".bmp", "image/tiff": ".tiff", "image/webp": ".webp",
|
||||
}
|
||||
|
||||
|
||||
def _collect_attachments(
|
||||
msg: email.message.Message, tmpdir: Path
|
||||
) -> list[ParsedAttachment]:
|
||||
"""Extract all attachment parts and write them to tmpdir.
|
||||
|
||||
Also captures inline images (CID-embedded) that have no filename.
|
||||
"""
|
||||
attachments: list[ParsedAttachment] = []
|
||||
seen_names: set[str] = set()
|
||||
inline_image_counter = 0
|
||||
|
||||
for part in msg.walk():
|
||||
disposition = str(part.get("Content-Disposition", ""))
|
||||
content_type = part.get_content_type()
|
||||
filename = part.get_filename()
|
||||
|
||||
# Inline image without a filename — generate one from Content-ID or counter
|
||||
if filename is None and content_type in _IMAGE_CONTENT_TYPES:
|
||||
cid = str(part.get("Content-ID", "")).strip("<>").split("@")[0]
|
||||
ext = _IMAGE_EXTS.get(content_type, ".img")
|
||||
filename = f"inline_{cid or inline_image_counter}{ext}"
|
||||
inline_image_counter += 1
|
||||
elif filename is None and "attachment" not in disposition:
|
||||
continue
|
||||
elif filename is None:
|
||||
# Unnamed non-image attachment — skip
|
||||
continue
|
||||
|
||||
# Decode RFC2047 filename if needed
|
||||
decoded_parts = email.header.decode_header(filename)
|
||||
filename_clean = ""
|
||||
for chunk, charset in decoded_parts:
|
||||
if isinstance(chunk, bytes):
|
||||
filename_clean += chunk.decode(charset or "utf-8", errors="replace")
|
||||
else:
|
||||
filename_clean += chunk
|
||||
|
||||
# Avoid duplicates
|
||||
base_name = filename_clean
|
||||
counter = 1
|
||||
while filename_clean in seen_names:
|
||||
stem = Path(base_name).stem
|
||||
suffix = Path(base_name).suffix
|
||||
filename_clean = f"{stem}_{counter}{suffix}"
|
||||
counter += 1
|
||||
seen_names.add(filename_clean)
|
||||
|
||||
payload = part.get_payload(decode=True)
|
||||
if payload is None:
|
||||
continue
|
||||
|
||||
dest = tmpdir / filename_clean
|
||||
dest.write_bytes(payload)
|
||||
|
||||
attachments.append(
|
||||
ParsedAttachment(
|
||||
filename=filename_clean,
|
||||
path=dest,
|
||||
content_type=part.get_content_type(),
|
||||
)
|
||||
)
|
||||
|
||||
return attachments
|
||||
|
||||
|
||||
def parse_eml(eml_path: Path) -> ParsedEmail:
|
||||
"""Parse an .eml file and return a ParsedEmail object.
|
||||
|
||||
The caller is responsible for calling parsed_email.cleanup() when done,
|
||||
or using ParsedEmail as a context manager is not implemented — keep
|
||||
the return value alive until you no longer need the attachment paths.
|
||||
"""
|
||||
with open(eml_path, "rb") as f:
|
||||
msg = email.message_from_binary_file(f, policy=email.policy.compat32)
|
||||
|
||||
subject = _decode_header_value(msg.get("Subject"))
|
||||
sender = _decode_header_value(msg.get("From"))
|
||||
recipient = _decode_header_value(msg.get("To"))
|
||||
date = _decode_header_value(msg.get("Date"))
|
||||
|
||||
body_text = _extract_body(msg)
|
||||
|
||||
tmpdir_obj = tempfile.TemporaryDirectory(prefix="email_dlp_")
|
||||
tmpdir = Path(tmpdir_obj.name)
|
||||
attachments = _collect_attachments(msg, tmpdir)
|
||||
|
||||
return ParsedEmail(
|
||||
subject=subject,
|
||||
sender=sender,
|
||||
recipient=recipient,
|
||||
date=date,
|
||||
body_text=body_text,
|
||||
attachments=attachments,
|
||||
_tempdir=tmpdir_obj,
|
||||
)
|
||||
132
email_dlp/policy.py
Normal file
132
email_dlp/policy.py
Normal file
@ -0,0 +1,132 @@
|
||||
"""DLP policy definitions: violation categories, thresholds, and prompt formatting."""
|
||||
|
||||
import json
|
||||
|
||||
from .models import ActionClass, RiskLevel
|
||||
|
||||
# Risk score thresholds
|
||||
RISK_THRESHOLDS = {
|
||||
RiskLevel.CRITICAL: 80,
|
||||
RiskLevel.HIGH: 60,
|
||||
RiskLevel.MEDIUM: 40,
|
||||
RiskLevel.LOW: 0,
|
||||
}
|
||||
|
||||
# Action mapping based on risk level
|
||||
RISK_TO_ACTION = {
|
||||
RiskLevel.CRITICAL: ActionClass.BLOCK,
|
||||
RiskLevel.HIGH: ActionClass.ALERT,
|
||||
RiskLevel.MEDIUM: ActionClass.ALERT,
|
||||
RiskLevel.LOW: ActionClass.PASS_,
|
||||
}
|
||||
|
||||
DLP_CATEGORIES = {
|
||||
"PII": {
|
||||
"description": "Personally Identifiable Information",
|
||||
"signals": [
|
||||
"Full name combined with email address",
|
||||
"Social Security Number (SSN) or employee ID",
|
||||
"Phone numbers combined with personal details",
|
||||
"Home address combined with personal identifiers",
|
||||
],
|
||||
"risk_weight": "HIGH to CRITICAL depending on volume",
|
||||
},
|
||||
"FINANCIAL_DATA": {
|
||||
"description": "Non-public financial information",
|
||||
"signals": [
|
||||
"Revenue targets, EBITDA projections, internal forecasts",
|
||||
"Salary figures, compensation plans",
|
||||
"Invoice amounts and vendor payment terms",
|
||||
"Internal budget allocations",
|
||||
],
|
||||
"risk_weight": "MEDIUM to CRITICAL depending on sensitivity",
|
||||
},
|
||||
"SOURCE_CODE": {
|
||||
"description": "Proprietary source code or model weights",
|
||||
"signals": [
|
||||
"Python, Java, or other source files with copyright notices",
|
||||
"Internal class names and proprietary algorithms",
|
||||
"Model architecture files or weight files",
|
||||
"Internal API keys or credentials embedded in code",
|
||||
],
|
||||
"risk_weight": "CRITICAL",
|
||||
},
|
||||
"REGULATORY_DOCUMENT": {
|
||||
"description": "Internal regulatory and compliance drafts",
|
||||
"signals": [
|
||||
"CFPB, GDPR, or SOX compliance drafts marked internal",
|
||||
"Audit findings or remediation plans",
|
||||
"Internal compliance assessments not yet published",
|
||||
"Regulatory submission drafts",
|
||||
],
|
||||
"risk_weight": "CRITICAL",
|
||||
},
|
||||
"LEGAL_CONTRACT": {
|
||||
"description": "Executed or draft legal agreements",
|
||||
"signals": [
|
||||
"Non-Disclosure Agreements (NDAs) with named parties",
|
||||
"Signed contracts with dates and signatures",
|
||||
"Settlement agreements or legal memoranda",
|
||||
"Vendor contracts with financial terms",
|
||||
],
|
||||
"risk_weight": "HIGH to CRITICAL",
|
||||
},
|
||||
"PAYROLL_RECORD": {
|
||||
"description": "Employee payroll and compensation records",
|
||||
"signals": [
|
||||
"Employee ID combined with salary and payroll period",
|
||||
"Direct deposit details or bank account information",
|
||||
"Year-to-date earnings and deductions",
|
||||
"HR compensation reports",
|
||||
],
|
||||
"risk_weight": "CRITICAL",
|
||||
},
|
||||
"CUSTOMER_LIST": {
|
||||
"description": "Customer or prospect data in bulk",
|
||||
"signals": [
|
||||
"CSV or table with customer names, emails, and revenue figures",
|
||||
"CRM exports with contact details",
|
||||
"Prospect lists for sales campaigns",
|
||||
"Customer PII in aggregate",
|
||||
],
|
||||
"risk_weight": "CRITICAL",
|
||||
},
|
||||
"INTERNAL_MEMO": {
|
||||
"description": "Confidential internal communications",
|
||||
"signals": [
|
||||
'Documents marked "INTERNAL ONLY" or "DO NOT DISTRIBUTE"',
|
||||
"CEO or executive strategy memos",
|
||||
"Organizational restructuring plans",
|
||||
"Internal performance reviews or headcount discussions",
|
||||
],
|
||||
"risk_weight": "HIGH",
|
||||
},
|
||||
}
|
||||
|
||||
ACTION_THRESHOLDS = {
|
||||
"BLOCK": "risk_score >= 80 (CRITICAL risk)",
|
||||
"ALERT": "risk_score >= 40 (MEDIUM or HIGH risk)",
|
||||
"PASS": "risk_score < 40 (LOW risk)",
|
||||
}
|
||||
|
||||
|
||||
def format_policy_for_prompt() -> str:
|
||||
"""Format the DLP policy as a JSON string for injection into the LLM system prompt."""
|
||||
policy = {
|
||||
"categories": DLP_CATEGORIES,
|
||||
"risk_score_thresholds": {
|
||||
"CRITICAL": "score >= 80",
|
||||
"HIGH": "score >= 60",
|
||||
"MEDIUM": "score >= 40",
|
||||
"LOW": "score < 40",
|
||||
},
|
||||
"action_mapping": ACTION_THRESHOLDS,
|
||||
"instructions": (
|
||||
"Evaluate the email against ALL categories above. "
|
||||
"Assign a risk_score from 0 to 100 based on the most severe violation found. "
|
||||
"Multiple violations increase the score. "
|
||||
"action must match the threshold: BLOCK if score>=80, ALERT if score>=40, PASS otherwise. "
|
||||
"evidence must be direct quotes from the actual email or attachment content."
|
||||
),
|
||||
}
|
||||
return json.dumps(policy, indent=2)
|
||||
285
email_dlp/policy_reviewer.py
Normal file
285
email_dlp/policy_reviewer.py
Normal file
@ -0,0 +1,285 @@
|
||||
"""Policy-based DLP review derived from DLP_CATEGORIES in policy.py."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from collections import defaultdict
|
||||
|
||||
from .models import ActionClass, AttachmentResult, DLPResult, RiskLevel, ViolationType
|
||||
from .policy import DLP_CATEGORIES
|
||||
|
||||
# Keywords derived from DLP_CATEGORIES signal descriptions in policy.py
|
||||
_POLICY_KEYWORDS: dict[ViolationType, dict] = {
|
||||
ViolationType.PII: {
|
||||
"keywords": [
|
||||
"full name",
|
||||
"email address",
|
||||
"social security",
|
||||
"ssn",
|
||||
"employee id",
|
||||
"phone number",
|
||||
"home address",
|
||||
"personal identifier",
|
||||
"date of birth",
|
||||
],
|
||||
"min_matches": 2,
|
||||
"base_score": 55,
|
||||
},
|
||||
ViolationType.FINANCIAL_DATA: {
|
||||
"keywords": [
|
||||
"revenue",
|
||||
"ebitda",
|
||||
"projection",
|
||||
"forecast",
|
||||
"salary",
|
||||
"compensation plan",
|
||||
"invoice",
|
||||
"amount due",
|
||||
"payment terms",
|
||||
"budget",
|
||||
"gross margin",
|
||||
"sales data",
|
||||
],
|
||||
"min_matches": 1,
|
||||
"base_score": 50,
|
||||
},
|
||||
ViolationType.SOURCE_CODE: {
|
||||
"keywords": [
|
||||
"copyright",
|
||||
"def ",
|
||||
"class ",
|
||||
"from __future__",
|
||||
"import ",
|
||||
"model weights",
|
||||
"api key",
|
||||
"api_key",
|
||||
"proprietary",
|
||||
"source code",
|
||||
"internal source",
|
||||
],
|
||||
"min_matches": 2,
|
||||
"base_score": 85,
|
||||
},
|
||||
ViolationType.REGULATORY_DOCUMENT: {
|
||||
"keywords": [
|
||||
"cfpb",
|
||||
"gdpr",
|
||||
"sox",
|
||||
"compliance draft",
|
||||
"not for public release",
|
||||
"not for public distribution",
|
||||
"regulatory submission",
|
||||
"audit findings",
|
||||
"remediation plan",
|
||||
"internal compliance",
|
||||
],
|
||||
"min_matches": 1,
|
||||
"base_score": 82,
|
||||
},
|
||||
ViolationType.LEGAL_CONTRACT: {
|
||||
"keywords": [
|
||||
"non-disclosure",
|
||||
"nondisclosure",
|
||||
"nda",
|
||||
"disclosing party",
|
||||
"receiving party",
|
||||
"confidentiality agreement",
|
||||
"settlement agreement",
|
||||
"executed contract",
|
||||
"signed contract",
|
||||
],
|
||||
"min_matches": 1,
|
||||
"base_score": 65,
|
||||
},
|
||||
ViolationType.PAYROLL_RECORD: {
|
||||
"keywords": [
|
||||
"payroll",
|
||||
"pay period",
|
||||
"pay stub",
|
||||
"direct deposit",
|
||||
"routing number",
|
||||
"bank account",
|
||||
"net pay",
|
||||
"gross pay",
|
||||
"tax deductions",
|
||||
"year-to-date",
|
||||
"ytd",
|
||||
"compensation record",
|
||||
],
|
||||
"min_matches": 1,
|
||||
"base_score": 88,
|
||||
},
|
||||
ViolationType.CUSTOMER_LIST: {
|
||||
"keywords": [
|
||||
"customer list",
|
||||
"customer_id",
|
||||
"customer id",
|
||||
"crm export",
|
||||
"prospect list",
|
||||
"top-tier prospect",
|
||||
"annual_sales",
|
||||
"company_name",
|
||||
"bulk export",
|
||||
"sales campaign",
|
||||
],
|
||||
"min_matches": 2,
|
||||
"base_score": 85,
|
||||
},
|
||||
ViolationType.INTERNAL_MEMO: {
|
||||
"keywords": [
|
||||
"internal only",
|
||||
"internal use only",
|
||||
"do not distribute",
|
||||
"not for external",
|
||||
"office of the ceo",
|
||||
"organizational priorities",
|
||||
"growth roadmap",
|
||||
"strictly confidential",
|
||||
"internal policy document",
|
||||
"headcount",
|
||||
],
|
||||
"min_matches": 1,
|
||||
"base_score": 55,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def _normalize(text: str) -> str:
|
||||
return re.sub(r"\s+", " ", text).strip()
|
||||
|
||||
|
||||
def _find_evidence(text: str, keyword: str) -> str | None:
|
||||
match = re.search(re.escape(keyword.strip()), text, flags=re.IGNORECASE)
|
||||
if not match:
|
||||
return None
|
||||
start = max(0, match.start() - 60)
|
||||
end = min(len(text), match.end() + 100)
|
||||
return _normalize(text[start:end])
|
||||
|
||||
|
||||
def _risk_level_from_score(score: int) -> RiskLevel:
|
||||
if score >= 80:
|
||||
return RiskLevel.CRITICAL
|
||||
if score >= 60:
|
||||
return RiskLevel.HIGH
|
||||
if score >= 40:
|
||||
return RiskLevel.MEDIUM
|
||||
return RiskLevel.LOW
|
||||
|
||||
|
||||
def _action_from_score(score: int) -> ActionClass:
|
||||
if score >= 80:
|
||||
return ActionClass.BLOCK
|
||||
if score >= 40:
|
||||
return ActionClass.ALERT
|
||||
return ActionClass.PASS_
|
||||
|
||||
|
||||
def review_corpus(
|
||||
email_file: str,
|
||||
subject: str,
|
||||
sender: str,
|
||||
recipient: str,
|
||||
date: str,
|
||||
body_text: str,
|
||||
attachment_texts: list[tuple[str, str]],
|
||||
attachment_results: list[AttachmentResult],
|
||||
processing_errors: list[str],
|
||||
) -> DLPResult:
|
||||
"""Judge an email using DLP_CATEGORIES signals from policy.py."""
|
||||
# Build full text corpus
|
||||
parts = [
|
||||
f"Subject: {subject}",
|
||||
f"From: {sender}",
|
||||
f"To: {recipient}",
|
||||
body_text,
|
||||
]
|
||||
for filename, text in attachment_texts:
|
||||
parts.append(f"Attachment: {filename}")
|
||||
parts.append(text)
|
||||
|
||||
raw = "\n".join(p for p in parts if p)
|
||||
lower = raw.lower()
|
||||
|
||||
evidence_map: dict[ViolationType, list[str]] = defaultdict(list)
|
||||
score_map: dict[ViolationType, int] = {}
|
||||
|
||||
for vtype, rule in _POLICY_KEYWORDS.items():
|
||||
keywords: list[str] = rule["keywords"]
|
||||
min_matches: int = rule["min_matches"]
|
||||
base_score: int = rule["base_score"]
|
||||
match_count = 0
|
||||
|
||||
for kw in keywords:
|
||||
if kw.lower() in lower:
|
||||
match_count += 1
|
||||
ev = _find_evidence(raw, kw)
|
||||
if ev and ev not in evidence_map[vtype]:
|
||||
evidence_map[vtype].append(ev)
|
||||
|
||||
if match_count < min_matches:
|
||||
continue
|
||||
|
||||
score = base_score + min(12, (match_count - 1) * 3)
|
||||
|
||||
# Context boost: external recipient domain
|
||||
recipient_lower = recipient.lower()
|
||||
if any(d in recipient_lower for d in ["gmail.com", "yahoo.com", "outlook.com", "hotmail.com"]):
|
||||
score += 6
|
||||
|
||||
score_map[vtype] = min(99, score)
|
||||
|
||||
if not score_map:
|
||||
category_desc = DLP_CATEGORIES # keep reference to show it's used
|
||||
_ = category_desc # suppress unused warning
|
||||
return DLPResult(
|
||||
email_file=email_file,
|
||||
subject=subject,
|
||||
sender=sender,
|
||||
recipient=recipient,
|
||||
date=date,
|
||||
risk_level=RiskLevel.LOW,
|
||||
risk_score=12,
|
||||
violation_types=[ViolationType.NONE],
|
||||
action=ActionClass.PASS_,
|
||||
summary="Policy review found no DLP category signals in this email.",
|
||||
evidence=[],
|
||||
attachments=attachment_results,
|
||||
processing_errors=processing_errors,
|
||||
)
|
||||
|
||||
ranked = sorted(score_map.items(), key=lambda x: x[1], reverse=True)
|
||||
violation_types = [vt for vt, _ in ranked[:3]]
|
||||
risk_score = ranked[0][1]
|
||||
if len(ranked) > 1:
|
||||
risk_score = min(99, risk_score + min(10, 3 * (len(ranked) - 1)))
|
||||
|
||||
evidence: list[str] = []
|
||||
for vt in violation_types:
|
||||
evidence.extend(evidence_map[vt][:2])
|
||||
evidence = evidence[:5]
|
||||
|
||||
risk_level = _risk_level_from_score(risk_score)
|
||||
action = _action_from_score(risk_score)
|
||||
|
||||
violation_labels = ", ".join(v.value for v in violation_types)
|
||||
summary = (
|
||||
f"Policy review flagged {violation_labels} with {risk_level.value} risk "
|
||||
f"(score {risk_score}) using DLP_CATEGORIES signals from policy.py."
|
||||
)
|
||||
|
||||
return DLPResult(
|
||||
email_file=email_file,
|
||||
subject=subject,
|
||||
sender=sender,
|
||||
recipient=recipient,
|
||||
date=date,
|
||||
risk_level=risk_level,
|
||||
risk_score=risk_score,
|
||||
violation_types=violation_types,
|
||||
action=action,
|
||||
summary=summary,
|
||||
evidence=evidence,
|
||||
attachments=attachment_results,
|
||||
processing_errors=processing_errors,
|
||||
)
|
||||
310
email_dlp/simulator.py
Normal file
310
email_dlp/simulator.py
Normal file
@ -0,0 +1,310 @@
|
||||
"""Deterministic local simulator for DLP analysis."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from collections import defaultdict
|
||||
|
||||
from .converter import IMAGE_SENTINEL
|
||||
from .models import ActionClass, AttachmentResult, DLPResult, RiskLevel, ViolationType
|
||||
|
||||
_CATEGORY_RULES: dict[ViolationType, dict[str, object]] = {
|
||||
ViolationType.PII: {
|
||||
"keywords": [
|
||||
"personally identifiable information",
|
||||
" pii",
|
||||
"employee id",
|
||||
"account ending",
|
||||
"direct deposit",
|
||||
"customer_id",
|
||||
"first_name",
|
||||
"last_name",
|
||||
],
|
||||
"base_score": 30,
|
||||
"min_matches": 2,
|
||||
},
|
||||
ViolationType.FINANCIAL_DATA: {
|
||||
"keywords": [
|
||||
"financial forecast",
|
||||
"revenue",
|
||||
"ebitda",
|
||||
"gross margin",
|
||||
"margin efficiency",
|
||||
"sales data",
|
||||
"annual_sales_usd",
|
||||
"invoice",
|
||||
"amount due",
|
||||
"payment instructions",
|
||||
"ach",
|
||||
"budget",
|
||||
],
|
||||
"base_score": 42,
|
||||
},
|
||||
ViolationType.SOURCE_CODE: {
|
||||
"keywords": [
|
||||
"source code",
|
||||
"api key",
|
||||
"model weights",
|
||||
"from __future__ import annotations",
|
||||
"def ",
|
||||
"class ",
|
||||
"@dataclass",
|
||||
],
|
||||
"base_score": 88,
|
||||
"min_matches": 2,
|
||||
},
|
||||
ViolationType.REGULATORY_DOCUMENT: {
|
||||
"keywords": [
|
||||
"regulatory document",
|
||||
"regulatory submission",
|
||||
"cfpb",
|
||||
"compliance report",
|
||||
"not for public release",
|
||||
"draft regulatory",
|
||||
"prepared by: legal & compliance team",
|
||||
],
|
||||
"base_score": 84,
|
||||
},
|
||||
ViolationType.LEGAL_CONTRACT: {
|
||||
"keywords": [
|
||||
"nondisclosure agreement",
|
||||
"non-disclosure agreement",
|
||||
"executed nda",
|
||||
"disclosing party",
|
||||
"receiving party",
|
||||
],
|
||||
"base_score": 62,
|
||||
"min_matches": 1,
|
||||
},
|
||||
ViolationType.PAYROLL_RECORD: {
|
||||
"keywords": [
|
||||
"payroll",
|
||||
"pay stub",
|
||||
"compensation record",
|
||||
"gross:",
|
||||
"net pay",
|
||||
"tax deductions",
|
||||
"pay period",
|
||||
"direct deposit",
|
||||
"employee id",
|
||||
],
|
||||
"base_score": 90,
|
||||
},
|
||||
ViolationType.CUSTOMER_LIST: {
|
||||
"keywords": [
|
||||
"customer list",
|
||||
"prospects",
|
||||
"crm export",
|
||||
"raw export",
|
||||
"customer_id",
|
||||
"company_name",
|
||||
"annual_sales_usd",
|
||||
"top-tier prospects",
|
||||
],
|
||||
"base_score": 86,
|
||||
"min_matches": 2,
|
||||
},
|
||||
ViolationType.INTERNAL_MEMO: {
|
||||
"keywords": [
|
||||
"internal use only",
|
||||
"internal memo",
|
||||
"do not distribute externally",
|
||||
"office of the ceo",
|
||||
"organizational priorities",
|
||||
"growth roadmap",
|
||||
"internal policy document",
|
||||
"not for public distribution",
|
||||
"strictly confidential",
|
||||
],
|
||||
"base_score": 52,
|
||||
"min_matches": 1,
|
||||
},
|
||||
}
|
||||
|
||||
_RISK_LEVELS = [
|
||||
(80, RiskLevel.CRITICAL),
|
||||
(60, RiskLevel.HIGH),
|
||||
(40, RiskLevel.MEDIUM),
|
||||
(0, RiskLevel.LOW),
|
||||
]
|
||||
|
||||
|
||||
def _normalize_text(text: str) -> str:
|
||||
return re.sub(r"\s+", " ", text).strip()
|
||||
|
||||
|
||||
def _build_corpus(
|
||||
subject: str,
|
||||
sender: str,
|
||||
recipient: str,
|
||||
body_text: str,
|
||||
attachment_texts: list[tuple[str, str]],
|
||||
) -> tuple[str, str]:
|
||||
text_chunks = [
|
||||
f"Subject: {subject}",
|
||||
f"From: {sender}",
|
||||
f"To: {recipient}",
|
||||
body_text,
|
||||
]
|
||||
for filename, text in attachment_texts:
|
||||
text_chunks.append(f"Attachment: {filename}")
|
||||
# Skip binary image data — base64 payloads produce false keyword matches
|
||||
if not text.startswith(IMAGE_SENTINEL):
|
||||
text_chunks.append(text)
|
||||
raw = "\n".join(chunk for chunk in text_chunks if chunk)
|
||||
return raw, raw.lower()
|
||||
|
||||
|
||||
def _find_evidence(text: str, keyword: str) -> str | None:
|
||||
pattern = re.escape(keyword.strip())
|
||||
match = re.search(pattern, text, flags=re.IGNORECASE)
|
||||
if not match:
|
||||
return None
|
||||
start = max(0, match.start() - 60)
|
||||
end = min(len(text), match.end() + 100)
|
||||
return _normalize_text(text[start:end])
|
||||
|
||||
|
||||
def _collect_matches(
|
||||
raw_text: str,
|
||||
lower_text: str,
|
||||
) -> tuple[dict[ViolationType, list[str]], dict[ViolationType, int]]:
|
||||
evidence_map: dict[ViolationType, list[str]] = defaultdict(list)
|
||||
score_map: dict[ViolationType, int] = {}
|
||||
|
||||
for violation_type, rule in _CATEGORY_RULES.items():
|
||||
keywords = rule["keywords"]
|
||||
base_score = int(rule["base_score"])
|
||||
min_matches = int(rule.get("min_matches", 1))
|
||||
match_count = 0
|
||||
|
||||
for keyword in keywords:
|
||||
# Use word boundaries to avoid substring false positives (e.g. "ach" in "attached")
|
||||
pattern = r"\b" + re.escape(keyword) + r"\b"
|
||||
if re.search(pattern, lower_text):
|
||||
match_count += 1
|
||||
evidence = _find_evidence(raw_text, keyword)
|
||||
if evidence and evidence not in evidence_map[violation_type]:
|
||||
evidence_map[violation_type].append(evidence)
|
||||
|
||||
if match_count < min_matches:
|
||||
continue
|
||||
|
||||
score = base_score + min(12, (match_count - 1) * 4)
|
||||
score_map[violation_type] = min(score, 99)
|
||||
|
||||
return evidence_map, score_map
|
||||
|
||||
|
||||
def _apply_context_boosts(
|
||||
subject: str,
|
||||
recipient: str,
|
||||
attachment_texts: list[tuple[str, str]],
|
||||
score_map: dict[ViolationType, int],
|
||||
) -> None:
|
||||
subject_lower = subject.lower()
|
||||
recipient_lower = recipient.lower()
|
||||
|
||||
if any(domain in recipient_lower for domain in ["gmail.com", "yahoo.com", "outlook.com", "hotmail.com"]):
|
||||
for violation_type in list(score_map):
|
||||
score_map[violation_type] = min(99, score_map[violation_type] + 6)
|
||||
|
||||
if "urgent" in subject_lower or "confidential" in subject_lower:
|
||||
for violation_type in list(score_map):
|
||||
score_map[violation_type] = min(99, score_map[violation_type] + 2)
|
||||
|
||||
attachment_names = " ".join(filename.lower() for filename, _ in attachment_texts)
|
||||
if ".csv" in attachment_names and ViolationType.CUSTOMER_LIST in score_map:
|
||||
score_map[ViolationType.CUSTOMER_LIST] = min(
|
||||
99, score_map[ViolationType.CUSTOMER_LIST] + 6
|
||||
)
|
||||
if ".py" in attachment_names and ViolationType.SOURCE_CODE in score_map:
|
||||
score_map[ViolationType.SOURCE_CODE] = min(
|
||||
99, score_map[ViolationType.SOURCE_CODE] + 4
|
||||
)
|
||||
|
||||
|
||||
def _risk_level_from_score(risk_score: int) -> RiskLevel:
|
||||
for threshold, risk_level in _RISK_LEVELS:
|
||||
if risk_score >= threshold:
|
||||
return risk_level
|
||||
return RiskLevel.LOW
|
||||
|
||||
|
||||
def _action_from_score(risk_score: int) -> ActionClass:
|
||||
if risk_score >= 80:
|
||||
return ActionClass.BLOCK
|
||||
if risk_score >= 40:
|
||||
return ActionClass.ALERT
|
||||
return ActionClass.PASS_
|
||||
|
||||
|
||||
def _build_summary(
|
||||
violation_types: list[ViolationType],
|
||||
risk_level: RiskLevel,
|
||||
risk_score: int,
|
||||
) -> str:
|
||||
if violation_types == [ViolationType.NONE]:
|
||||
return "No strong DLP indicators were found in the email body or converted attachments."
|
||||
labels = ", ".join(v.value for v in violation_types)
|
||||
return (
|
||||
f"Simulated DLP review flagged {labels} with {risk_level.value} risk "
|
||||
f"(score {risk_score}) based on the email body and extracted attachment content."
|
||||
)
|
||||
|
||||
|
||||
def simulate_analysis(
|
||||
email_file: str,
|
||||
subject: str,
|
||||
sender: str,
|
||||
recipient: str,
|
||||
date: str,
|
||||
body_text: str,
|
||||
attachment_texts: list[tuple[str, str]],
|
||||
attachment_results: list[AttachmentResult],
|
||||
processing_errors: list[str],
|
||||
) -> DLPResult:
|
||||
"""Predict a DLP result locally without calling an LLM."""
|
||||
raw_text, lower_text = _build_corpus(
|
||||
subject=subject,
|
||||
sender=sender,
|
||||
recipient=recipient,
|
||||
body_text=body_text,
|
||||
attachment_texts=attachment_texts,
|
||||
)
|
||||
evidence_map, score_map = _collect_matches(raw_text, lower_text)
|
||||
_apply_context_boosts(subject, recipient, attachment_texts, score_map)
|
||||
|
||||
if not score_map:
|
||||
violation_types = [ViolationType.NONE]
|
||||
risk_score = 18
|
||||
evidence: list[str] = []
|
||||
else:
|
||||
ranked = sorted(score_map.items(), key=lambda item: item[1], reverse=True)
|
||||
violation_types = [violation for violation, _ in ranked[:3]]
|
||||
risk_score = ranked[0][1]
|
||||
if len(ranked) > 1:
|
||||
risk_score = min(99, risk_score + min(10, 3 * (len(ranked) - 1)))
|
||||
evidence = []
|
||||
for violation_type in violation_types:
|
||||
evidence.extend(evidence_map.get(violation_type, [])[:2])
|
||||
evidence = evidence[:5]
|
||||
|
||||
risk_level = _risk_level_from_score(risk_score)
|
||||
action = _action_from_score(risk_score)
|
||||
|
||||
return DLPResult(
|
||||
email_file=email_file,
|
||||
subject=subject,
|
||||
sender=sender,
|
||||
recipient=recipient,
|
||||
date=date,
|
||||
risk_level=risk_level,
|
||||
risk_score=risk_score,
|
||||
violation_types=violation_types,
|
||||
action=action,
|
||||
summary=_build_summary(violation_types, risk_level, risk_score),
|
||||
evidence=evidence,
|
||||
attachments=attachment_results,
|
||||
processing_errors=processing_errors,
|
||||
)
|
||||
26
pyproject.toml
Normal file
26
pyproject.toml
Normal file
@ -0,0 +1,26 @@
|
||||
[project]
|
||||
name = "email-dlp"
|
||||
version = "0.1.0"
|
||||
description = "Email DLP proof-of-concept using local LLM"
|
||||
requires-python = ">=3.11"
|
||||
dependencies = [
|
||||
"markitdown[pdf]>=0.1.0",
|
||||
"py7zr>=0.21.0",
|
||||
"openai>=1.30.0",
|
||||
"pydantic>=2.0",
|
||||
"beautifulsoup4>=4.12",
|
||||
"rich>=13.0",
|
||||
"typer>=0.12",
|
||||
"python-dotenv>=1.0.0",
|
||||
"pymupdf>=1.27.2",
|
||||
]
|
||||
|
||||
[project.scripts]
|
||||
email-dlp = "email_dlp.cli:app"
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
[tool.hatch.build.targets.wheel]
|
||||
packages = ["email_dlp"]
|
||||
Reference in New Issue
Block a user