项目文件夹

文件
wehub-resource-sync 7a0da7932b
OSV-Scanner (Scheduled) / scan-scheduled (push) Failing after 0s
Create Release / test-gate (push) Has been cancelled
Create Release / release-gate (push) Has been cancelled
Create Release / ci-gate (push) Has been cancelled
Create Release / version-check (push) Has been cancelled
Create Release / e2e-test-gate (push) Has been cancelled
Create Release / responsive-test-gate (push) Has been cancelled
Create Release / compat-test-gate (push) Has been cancelled
Create Release / compose-integration-gate (push) Has been cancelled
Create Release / vulture-gate (push) Has been cancelled
Create Release / build (push) Has been cancelled
Create Release / provenance (push) Has been cancelled
Create Release / prerelease-docker (push) Has been cancelled
Create Release / publish-docker (push) Has been cancelled
Create Release / create-release (push) Has been cancelled
Create Release / cleanup-changelog (push) Has been cancelled
Create Release / trigger-pypi (push) Has been cancelled
Create Release / monitor-pypi (push) Has been cancelled
Create Release / Clean up orphan prerelease tags and signatures (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-form] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-metrics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-workflow] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-core] (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [history-news] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [library] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [link-analytics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-core] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-lifecycle] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [error-benchmark] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) (push) Has been cancelled
Docker Tests (Consolidated) / Accessibility Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Unit Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Example Tests (push) Has been cancelled
Docker Tests (Consolidated) / Production Image Smoke Test (push) Has been cancelled
Docker Tests (Consolidated) / Infrastructure Tests (push) Has been cancelled
OSSF Scorecard / OSSF Security Scorecard Analysis (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [mobile] (push) Has been cancelled
Backwards Compatibility / Verify Encryption Constants (push) Has been cancelled
Backwards Compatibility / PyPI Version Compatibility (push) Has been cancelled
Backwards Compatibility / Database Migration Tests (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Docker Tests (Consolidated) / detect-changes (push) Has been cancelled
Docker Tests (Consolidated) / Build Test Image (push) Has been cancelled
Docker Tests (Consolidated) / All Pytest Tests + Coverage (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [accessibility] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [api-crud] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-login] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-register] (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:08:55 +08:00

175 行
5.7 KiB
Python

"""Regression: subscription_id LIKE wildcards must be literal (#3094).
``get_news_feed`` filters research history by substring-matching the
caller-supplied ``subscription_id`` into a ``research_meta LIKE`` pattern.
Without escaping, a ``subscription_id`` containing ``%`` or ``_`` acts as a
SQL wildcard and pulls research rows belonging to *other* subscriptions
(a wildcard-injection / enumeration bug — reachable unvalidated via the
``news/flask_api.py`` blueprint).
These use a real in-memory SQLite session (only the per-user DB session is
patched) so SQLite itself evaluates the ``LIKE ... ESCAPE`` pattern; they
fail if the escaping at ``news/api.py`` is reverted.
"""
from contextlib import contextmanager
from datetime import datetime, UTC
from unittest.mock import patch
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from local_deep_research.database.models import Base, ResearchHistory
@contextmanager
def _patched_user_db(seed_fn):
"""Seed a fresh in-memory DB via ``seed_fn(session)`` and patch the
per-user DB session so ``news.api`` uses it. Only the session is
patched; SQLite itself evaluates the real ``LIKE ... ESCAPE`` pattern.
"""
engine = create_engine("sqlite:///:memory:")
Base.metadata.create_all(engine)
session_local = sessionmaker(bind=engine)
seed = session_local()
seed_fn(seed)
seed.commit()
seed.close()
@contextmanager
def _fake_user_db(username=None, password=None):
db = session_local()
try:
yield db
finally:
db.close()
with patch(
"local_deep_research.database.session_context.get_user_db_session",
_fake_user_db,
):
yield
engine.dispose()
def _news_row(research_id, subscription_id, now):
"""A completed news-style research row bound to a subscription."""
return ResearchHistory(
id=research_id,
query="latest news on the topic",
title="Topic",
mode="quick",
status="completed",
created_at=now,
completed_at=now,
report_content="Answer.",
research_meta={
"is_news_search": True,
"subscription_id": subscription_id,
},
)
@contextmanager
def _seeded_feed(rows):
"""Seed ResearchHistory news rows and patch the per-user DB session.
``rows`` is an iterable of ``(research_id, subscription_id)``. The query
text and ``is_news_search`` flag make ``get_news_feed`` surface each row
as a news item, so the only thing distinguishing target from decoy is
the ``subscription_id`` the LIKE filter matches on.
"""
now = datetime.now(UTC).isoformat()
def _seed(session):
for research_id, subscription_id in rows:
session.add(_news_row(research_id, subscription_id, now))
with _patched_user_db(_seed):
yield
def _feed_research_ids(subscription_id):
from local_deep_research.news.api import get_news_feed
result = get_news_feed(
user_id="testuser",
limit=20,
use_cache=False,
subscription_id=subscription_id,
)
return [it.get("research_id") for it in result.get("news_items", [])]
def test_subscription_underscore_treated_as_literal():
"""Filtering on 'a_b' must not pull the 'aXb' subscription's rows."""
with _seeded_feed([("target", "a_b"), ("decoy", "aXb")]):
ids = _feed_research_ids("a_b")
assert "target" in ids
assert "decoy" not in ids
def test_subscription_percent_treated_as_literal():
"""Filtering on 'sub-100%' must not pull 'sub-100X-extra' rows."""
with _seeded_feed([("target", "sub-100%"), ("decoy", "sub-100X-extra")]):
ids = _feed_research_ids("sub-100%")
assert "target" in ids
assert "decoy" not in ids
def test_get_subscriptions_run_count_ignores_wildcard_matches():
"""get_subscriptions' per-subscription total_runs must count only the
exact subscription: id 'a_b' must not also count the 'aXb' run.
Real-DB fail-on-revert coverage for the get_subscriptions LIKE site.
"""
from local_deep_research.database.models.news import NewsSubscription
from local_deep_research.news.api import get_subscriptions
def _seed(session):
session.add(
NewsSubscription(
id="a_b", subscription_type="search", query_or_topic="q"
)
)
session.add(_news_row("target", "a_b", "2026-01-01T00:00:00"))
session.add(_news_row("decoy", "aXb", "2026-01-01T00:00:00"))
with _patched_user_db(_seed):
subscriptions = get_subscriptions("testuser")["subscriptions"]
by_id = {s["id"]: s for s in subscriptions}
assert by_id["a_b"]["total_runs"] == 1
def test_get_subscription_history_ignores_wildcard_matches():
"""get_subscription_history for 'a_b' must not return the 'aXb'
subscription's research runs.
Real-DB fail-on-revert coverage for the get_subscription_history LIKE
site. Because it exercises the full return path it also guards the
created_at/completed_at Text handling (a reverted `.isoformat()` on the
Text column would raise here).
"""
from local_deep_research.database.models.news import NewsSubscription
from local_deep_research.news.api import get_subscription_history
def _seed(session):
session.add(
NewsSubscription(
id="a_b", subscription_type="search", query_or_topic="q"
)
)
session.add(_news_row("target", "a_b", "2026-01-01T00:00:00"))
session.add(_news_row("decoy", "aXb", "2026-01-01T00:00:00"))
with _patched_user_db(_seed):
history = get_subscription_history("a_b")["history"]
ids = [h["research_id"] for h in history]
assert "target" in ids
assert "decoy" not in ids