Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 12 additions & 5 deletions .github/workflows/update-qpk-pin.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,21 +66,28 @@ jobs:
python -c "from quant_platform_kit.common.contracts import SnapshotProfileContract; print('contracts OK')"

# Verify strategy repos installable with NEW constraints
failed=0
for dep in us-equity-strategies hk-equity-strategies cn-equity-strategies crypto-strategies; do
echo "Checking $dep..."
python -m pip install --dry-run -c constraints.txt "$dep" >/dev/null 2>&1 && echo " $dep OK" || echo " $dep FAILED"
if python -m pip install --dry-run -c constraints.txt "$dep" >/dev/null 2>&1; then
echo " $dep OK"
else
echo " $dep FAILED"
failed=1
fi
done
if [ "${failed}" -ne 0 ]; then
echo "One or more downstream dependency checks failed." >&2
exit 1
fi
echo "::endgroup::"
echo "All compatibility checks passed."

- name: Create PR for pin update
if: steps.update.outputs.changed == 'true'
uses: peter-evans/create-pull-request@v7
with:
# Use PAT to bypass GH013 ruleset (GITHUB_TOKEN is blocked from creating PRs).
# Create a classic PAT at https://github.com/settings/tokens with `public_repo`
# scope, then add it as org secret `QSL_AUTOMATION_PAT`.
token: ${{ secrets.QSL_AUTOMATION_PAT || secrets.GITHUB_TOKEN }}
token: ${{ github.token }}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Use a token that can trigger PR checks

For the generated auto/qpk-pin-update PRs, ${{ github.token }} is the repository GITHUB_TOKEN; the create-pull-request action docs note that PRs created with the default token do not trigger on: pull_request or on: push workflows. I checked this repo's PR validation workflows (.github/workflows/ci.yml and .github/workflows/codex_review_gate.yml) and they are pull_request-triggered, so these automated pin PRs will be opened without the normal CI/gate checks unless someone manually retriggers them. Use a policy-approved PAT/GitHub App token or add an explicit follow-up trigger for validation.

Useful? React with 👍 / 👎.

commit-message: "chore: auto-update QPK_PIN and constraints.txt"
title: "chore: auto-update QPK_PIN and constraints.txt"
body: |
Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[build-system]
requires = ["setuptools>=68"]
requires = ["setuptools>=77"]
build-backend = "setuptools.build_meta"

[project]
Expand All @@ -8,7 +8,7 @@ version = "0.10.0"
description = "QuantStrategyLab shared runtime: broker adapters, domain models, execution ports, cloud provider abstraction, and notification utilities."
readme = "README.md"
requires-python = ">=3.10"
license = { text = "MIT" }
license = "MIT"
authors = [
{ name = "QuantStrategyLab" }
]
Expand Down
2 changes: 1 addition & 1 deletion scripts/gate_codex_app_review.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ def scan_diff(diff_text: str, path_patterns: list[re.Pattern[str]]) -> list[str]
if not line.startswith("+") or line.startswith("+++"): continue
m = _SENSITIVE.search(line[1:])
if m:
violations.append(f"**Hardcoded secret** in `{current}`: `{m.group(0)[:100]}`")
violations.append(f"**Hardcoded secret** in `{current}`: sensitive assignment pattern detected")
return list(dict.fromkeys(violations))


Expand Down
4 changes: 3 additions & 1 deletion src/quant_platform_kit/notifications/_email.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
from collections.abc import Sequence
from email.message import EmailMessage

from ._redaction import redact_sensitive_text


def parse_email_recipients(raw_value: str | Sequence[str] | None) -> tuple[str, ...]:
if raw_value is None:
Expand Down Expand Up @@ -63,5 +65,5 @@ def send_smtp_email(
smtp.send_message(message)
return True
except Exception as exc:
printer(f"Email send failed: {exc}", flush=True)
printer(f"Email send failed: {redact_sensitive_text(exc)}", flush=True)
return False
26 changes: 26 additions & 0 deletions src/quant_platform_kit/notifications/_redaction.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"""Small helpers for keeping notification errors safe to log."""

from __future__ import annotations

import re


_REDACTED = "<redacted>"
_TELEGRAM_BOT_PATH_RE = re.compile(r"(?i)(/bot)([^/\s]+)")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Redact webhook path keys before logging errors

This helper only masks Telegram-style /bot... path credentials and query-string secrets, but supported webhook providers also put credentials in URL paths (Feishu uses .../hook/KEY and ServerChan uses SENDKEY.send). When the webhook error path logs an opener/proxy exception that includes the full request URL, those provider keys remain unredacted, so the new log redaction still leaks secrets for two supported webhook formats. Add provider path patterns or parse URLs and mask sensitive path segments before logging.

Useful? React with 👍 / 👎.

_SENSITIVE_QUERY_RE = re.compile(
r"(?i)([?&](?:access[_-]?token|api[_-]?key|auth[_-]?token|key|password|secret|signature|token)=)([^&\s]+)"
)
_AUTH_HEADER_RE = re.compile(r"(?i)\b(Bearer|Basic)\s+([A-Za-z0-9._~+/=-]{8,})")
_ASSIGNMENT_RE = re.compile(
r"(?i)\b(api[_-]?key|auth[_-]?token|credential|password|private[_-]?key|secret|token)\s*[:=]\s*([\"']?)([^\"'\s,;]{8,})([\"']?)"
)


def redact_sensitive_text(value: object) -> str:
"""Return text suitable for logs without exposing common secret shapes."""

text = str(value)
text = _TELEGRAM_BOT_PATH_RE.sub(r"\1" + _REDACTED, text)
text = _SENSITIVE_QUERY_RE.sub(r"\1" + _REDACTED, text)
text = _AUTH_HEADER_RE.sub(r"\1 " + _REDACTED, text)
return _ASSIGNMENT_RE.sub(lambda match: f"{match.group(1)}={_REDACTED}", text)
4 changes: 3 additions & 1 deletion src/quant_platform_kit/notifications/push.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
from email.header import Header
from typing import Any

from ._redaction import redact_sensitive_text


PUSH_PROVIDER_NTFY = "ntfy"
PUSH_PROVIDER_PUSHOVER = "pushover"
Expand Down Expand Up @@ -190,7 +192,7 @@ def _request_succeeded(
status = response.getcode()
status = int(status)
except Exception as exc:
printer(f"Push send failed for {recipient}: {exc}", flush=True)
printer(f"Push send failed for {recipient}: {redact_sensitive_text(exc)}", flush=True)
return False
if status < 200 or status >= 300:
printer(f"Push send failed for {recipient}: HTTP {status}", flush=True)
Expand Down
4 changes: 3 additions & 1 deletion src/quant_platform_kit/notifications/sms.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
from collections.abc import Sequence
from typing import Any

from ._redaction import redact_sensitive_text


def normalize_sms_recipient(value: str) -> str:
"""Normalize common US phone-number formatting to E.164.
Expand Down Expand Up @@ -102,7 +104,7 @@ def send_twilio_sms(
status = response.getcode()
status = int(status)
except Exception as exc:
printer(f"SMS send failed for {recipient}: {exc}", flush=True)
printer(f"SMS send failed for {recipient}: {redact_sensitive_text(exc)}", flush=True)
all_sent = False
continue
if status < 200 or status >= 300:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
)

from .alert_marker import CloudAlertMarkerStore, _clean_relative_key
from ._redaction import redact_sensitive_text
from ._email import parse_email_recipients, send_smtp_email


Expand Down Expand Up @@ -199,7 +200,7 @@ def publish_strategy_plugin_email_alerts(
store_error = None
except Exception as exc:
duplicate = False
store_error = f"alert_store_check_failed:{type(exc).__name__}: {exc}"
store_error = f"alert_store_check_failed:{type(exc).__name__}: {redact_sensitive_text(exc)}"
if duplicate:
deliveries.append(_delivery(message, status="skipped", reason="duplicate_alert"))
continue
Expand Down Expand Up @@ -252,7 +253,7 @@ def _send_message(
timeout=settings.timeout,
)
except Exception as exc:
return False, f"{type(exc).__name__}: {exc}"
return False, f"{type(exc).__name__}: {redact_sensitive_text(exc)}"
return bool(sent), None


Expand Down Expand Up @@ -284,7 +285,7 @@ def _store_record_error(
},
)
except Exception as exc:
return f"alert_store_record_failed:{type(exc).__name__}: {exc}"
return f"alert_store_record_failed:{type(exc).__name__}: {redact_sensitive_text(exc)}"
return None


Expand Down Expand Up @@ -350,4 +351,3 @@ def _fallback_alert_key(message: StrategyPluginAlertMessage) -> str:




Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
)

from .alert_marker import CloudAlertMarkerStore, _clean_relative_key
from ._redaction import redact_sensitive_text
from .push import (
DEFAULT_NTFY_API_BASE_URL,
DEFAULT_PUSHOVER_API_BASE_URL,
Expand Down Expand Up @@ -182,7 +183,7 @@ def publish_strategy_plugin_push_alerts(
store_error = None
except Exception as exc:
duplicate = False
store_error = f"alert_store_check_failed:{type(exc).__name__}: {exc}"
store_error = f"alert_store_check_failed:{type(exc).__name__}: {redact_sensitive_text(exc)}"
if duplicate:
deliveries.append(_delivery(message, status="skipped", reason="duplicate_alert"))
continue
Expand Down Expand Up @@ -235,7 +236,7 @@ def _send_message(
timeout=settings.timeout,
)
except Exception as exc:
return False, f"{type(exc).__name__}: {exc}"
return False, f"{type(exc).__name__}: {redact_sensitive_text(exc)}"
return bool(sent), None


Expand Down Expand Up @@ -275,7 +276,7 @@ def _store_record_error(
},
)
except Exception as exc:
return f"alert_store_record_failed:{type(exc).__name__}: {exc}"
return f"alert_store_record_failed:{type(exc).__name__}: {redact_sensitive_text(exc)}"
return None


Expand Down Expand Up @@ -340,4 +341,3 @@ def _fallback_alert_key(message: StrategyPluginAlertMessage) -> str:




8 changes: 4 additions & 4 deletions src/quant_platform_kit/notifications/strategy_plugin_sms.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
)

from .alert_marker import CloudAlertMarkerStore, _clean_relative_key
from ._redaction import redact_sensitive_text
from .sms import parse_sms_recipients, send_twilio_sms

_DEFAULT_SMS_PROVIDER = "twilio"
Expand Down Expand Up @@ -171,7 +172,7 @@ def publish_strategy_plugin_sms_alerts(
store_error = None
except Exception as exc:
duplicate = False
store_error = f"alert_store_check_failed:{type(exc).__name__}: {exc}"
store_error = f"alert_store_check_failed:{type(exc).__name__}: {redact_sensitive_text(exc)}"
if duplicate:
deliveries.append(_delivery(message, status="skipped", reason="duplicate_alert"))
continue
Expand Down Expand Up @@ -221,7 +222,7 @@ def _send_message(
timeout=settings.timeout,
)
except Exception as exc:
return False, f"{type(exc).__name__}: {exc}"
return False, f"{type(exc).__name__}: {redact_sensitive_text(exc)}"
return bool(sent), None


Expand Down Expand Up @@ -261,7 +262,7 @@ def _store_record_error(
},
)
except Exception as exc:
return f"alert_store_record_failed:{type(exc).__name__}: {exc}"
return f"alert_store_record_failed:{type(exc).__name__}: {redact_sensitive_text(exc)}"
return None


Expand Down Expand Up @@ -320,4 +321,3 @@ def _fallback_alert_key(message: StrategyPluginAlertMessage) -> str:




Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
)

from .alert_marker import CloudAlertMarkerStore, _clean_relative_key
from ._redaction import redact_sensitive_text
from .telegram import (
DEFAULT_TELEGRAM_BOT_API_BASE_URL,
parse_telegram_chat_ids,
Expand Down Expand Up @@ -168,7 +169,7 @@ def publish_strategy_plugin_telegram_alerts(
store_error = None
except Exception as exc:
duplicate = False
store_error = f"alert_store_check_failed:{type(exc).__name__}: {exc}"
store_error = f"alert_store_check_failed:{type(exc).__name__}: {redact_sensitive_text(exc)}"
if duplicate:
deliveries.append(_delivery(message, status="skipped", reason="duplicate_alert"))
continue
Expand Down Expand Up @@ -218,7 +219,7 @@ def _send_message(
timeout=settings.timeout,
)
except Exception as exc:
return False, f"{type(exc).__name__}: {exc}"
return False, f"{type(exc).__name__}: {redact_sensitive_text(exc)}"
return bool(sent), None


Expand Down Expand Up @@ -258,7 +259,7 @@ def _store_record_error(
},
)
except Exception as exc:
return f"alert_store_record_failed:{type(exc).__name__}: {exc}"
return f"alert_store_record_failed:{type(exc).__name__}: {redact_sensitive_text(exc)}"
return None


Expand Down Expand Up @@ -328,4 +329,3 @@ def _fallback_alert_key(message: StrategyPluginAlertMessage) -> str:




Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
)

from .alert_marker import CloudAlertMarkerStore, _clean_relative_key
from ._redaction import redact_sensitive_text
from .webhook import (
WEBHOOK_PROVIDER_WECOM,
WEBHOOK_PROVIDER_DINGTALK,
Expand Down Expand Up @@ -253,7 +254,7 @@ def publish_strategy_plugin_webhook_alerts(
store_error = None
except Exception as exc:
duplicate = False
store_error = f"alert_store_check_failed:{type(exc).__name__}: {exc}"
store_error = f"alert_store_check_failed:{type(exc).__name__}: {redact_sensitive_text(exc)}"
if duplicate:
deliveries.append(
_delivery(message, status="skipped", reason="duplicate_alert")
Expand Down Expand Up @@ -356,7 +357,7 @@ def _send_message(
timeout=settings.timeout,
)
except Exception as exc:
return False, f"{type(exc).__name__}: {exc}"
return False, f"{type(exc).__name__}: {redact_sensitive_text(exc)}"
return bool(sent), None


Expand Down Expand Up @@ -396,7 +397,7 @@ def _store_record_error(
},
)
except Exception as exc:
return f"alert_store_record_failed:{type(exc).__name__}: {exc}"
return f"alert_store_record_failed:{type(exc).__name__}: {redact_sensitive_text(exc)}"
return None


Expand Down
4 changes: 3 additions & 1 deletion src/quant_platform_kit/notifications/telegram.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
from collections.abc import Sequence
from typing import Any

from ._redaction import redact_sensitive_text


DEFAULT_TELEGRAM_BOT_API_BASE_URL = "https://api.telegram.org"
_TELEGRAM_MARKET_SYMBOL_LINK_RE = re.compile(r"(?<![A-Za-z0-9_])([A-Z0-9]{1,12})\.([A-Z]{2,4})(?![A-Za-z0-9_])")
Expand Down Expand Up @@ -127,7 +129,7 @@ def _request_succeeded(
status = response.getcode()
status = int(status)
except Exception as exc:
printer(f"Telegram send failed for {chat_id}: {exc}", flush=True)
printer(f"Telegram send failed for {chat_id}: {redact_sensitive_text(exc)}", flush=True)
return False
if status < 200 or status >= 300:
printer(f"Telegram send failed for {chat_id}: HTTP {status}", flush=True)
Expand Down
6 changes: 4 additions & 2 deletions src/quant_platform_kit/notifications/webhook.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
from collections.abc import Sequence
from typing import Any

from ._redaction import redact_sensitive_text


# ──────────────────────────────────────────────────────────────────────
# Provider constants
Expand Down Expand Up @@ -309,7 +311,7 @@ def _json_webhook_request_succeeded(
status = int(status)
raw = response.read()
except Exception as exc:
printer(f"{provider} webhook send failed: {exc}", flush=True)
printer(f"{provider} webhook send failed: {redact_sensitive_text(exc)}", flush=True)
return False
if status < 200 or status >= 300:
printer(f"{provider} webhook send failed: HTTP {status}", flush=True)
Expand All @@ -318,7 +320,7 @@ def _json_webhook_request_succeeded(
body = json.loads(raw.decode("utf-8"))
if body.get(errcode_key, -1) != 0:
printer(
f"{provider} webhook send failed: {body.get(errmsg_key, 'unknown error')}",
f"{provider} webhook send failed: {redact_sensitive_text(body.get(errmsg_key, 'unknown error'))}",
flush=True,
)
return False
Expand Down
Loading
Loading