nene2-python 1.8.42__tar.gz → 1.8.43__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {nene2_python-1.8.42 → nene2_python-1.8.43}/PKG-INFO +1 -1
- nene2_python-1.8.43/docs/field-trials/2026-05-field-trial-172.md +382 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/templates/field-trial-report.md +62 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/pyproject.toml +1 -1
- {nene2_python-1.8.42 → nene2_python-1.8.43}/uv.lock +1 -1
- {nene2_python-1.8.42 → nene2_python-1.8.43}/.env.example +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/.github/workflows/ci.yml +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/.github/workflows/docs.yml +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/.github/workflows/publish.yml +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/.gitignore +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/.vitepress/config.mts +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/.vitepress/theme/custom.css +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/.vitepress/theme/index.ts +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/AGENTS.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/CHANGELOG.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/CLAUDE.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/Dockerfile +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/LICENSE +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/README.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/alembic/README +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/alembic/env.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/alembic/script.py.mako +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/alembic/versions/001_create_notes_and_tags_tables.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/alembic.ini +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/compose.yaml +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/adr/0001-toolchain.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/adr/0002-clean-architecture.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/adr/0003-security-first.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/adr/0004-ai-first-design.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/adr/0005-logging.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/adr/0006-rate-limiting.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/adr/0009-mcp-design.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/adr/0010-async-use-case.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/adr/0011-mcp-as-core-dependency.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/de/index.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/de/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/explanation/architecture.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/explanation/design-philosophy.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-1.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-10.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-100.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-101.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-102.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-103.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-104.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-105.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-106.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-107.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-108.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-109.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-11.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-110.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-111.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-112.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-113.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-114.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-115.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-116.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-117.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-118.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-119.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-12.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-120.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-121.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-122.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-123.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-124.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-125.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-126.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-127.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-128.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-129.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-13.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-130.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-131.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-132.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-133.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-134.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-135.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-136.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-137.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-138.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-139.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-14.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-140.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-141.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-142.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-143.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-144.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-145.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-146.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-147.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-148.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-149.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-15.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-150.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-151.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-152.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-153.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-154.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-155.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-156.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-157.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-158.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-159.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-16.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-160.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-161.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-162.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-163.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-164.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-165.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-166.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-167.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-168.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-169.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-17.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-170.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-171.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-18.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-19.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-2.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-20.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-21.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-22.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-23.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-24.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-25.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-26.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-27.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-28.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-29.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-3.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-30.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-31.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-32.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-33.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-34.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-35.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-36.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-37.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-38.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-39.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-4.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-40.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-41.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-42.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-43.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-44.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-45.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-46.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-47.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-48.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-49.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-5.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-50.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-51.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-52.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-53.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-54.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-55.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-56.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-57.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-58.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-59.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-6.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-60.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-61.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-62.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-63.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-64.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-65.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-66.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-67.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-68.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-69.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-7.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-70.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-71.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-72.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-73.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-74.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-75.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-76.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-77.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-78.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-79.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-8.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-80.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-81.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-82.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-83.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-84.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-85.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-86.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-87.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-88.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-89.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-9.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-90.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-91.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-92.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-93.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-94.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-95.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-96.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-97.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-98.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/field-trials/2026-05-field-trial-99.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/fr/index.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/fr/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/how-to/add-new-domain.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/how-to/api-versioning.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/how-to/async-use-case.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/how-to/background-tasks.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/how-to/configure-auth.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/how-to/cors.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/how-to/custom-auth-middleware.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/how-to/dependency-injection.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/how-to/domain-events.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/how-to/file-upload.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/how-to/lifespan-and-app-state.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/how-to/middleware-stack.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/how-to/new-project.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/how-to/problem-details.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/how-to/response-patterns.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/how-to/run-tests.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/how-to/soft-delete.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/how-to/sqlalchemy-repository.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/how-to/streaming.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/how-to/structured-logging.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/how-to/validation.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/how-to/webhook.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/howto/mcp-setup.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/index.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/ja/explanation/architecture.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/ja/explanation/design-philosophy.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/ja/how-to/add-new-domain.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/ja/how-to/configure-auth.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/ja/how-to/new-project.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/ja/how-to/run-tests.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/ja/how-to/sqlalchemy-repository.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/ja/howto/mcp-setup.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/ja/index.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/ja/reference/api.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/ja/reference/configuration.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/ja/reference/framework-modules.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/ja/tutorials/first-domain.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/ja/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/pt-br/index.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/pt-br/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/reference/api.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/reference/configuration.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/reference/framework-modules.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/roadmap.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/todo/current.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/tutorials/first-domain.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/zh/index.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/docs/zh/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/package-lock.json +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/package.json +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/src/example/__init__.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/src/example/__main__.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/src/example/app.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/src/example/comment/__init__.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/src/example/comment/entity.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/src/example/comment/exceptions.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/src/example/comment/handler.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/src/example/comment/repository.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/src/example/comment/sqlalchemy_repository.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/src/example/comment/use_case.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/src/example/mcp.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/src/example/note/__init__.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/src/example/note/async_use_case.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/src/example/note/entity.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/src/example/note/exceptions.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/src/example/note/handler.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/src/example/note/repository.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/src/example/note/sqlalchemy_repository.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/src/example/note/use_case.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/src/example/schema.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/src/example/tag/__init__.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/src/example/tag/entity.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/src/example/tag/exceptions.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/src/example/tag/handler.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/src/example/tag/repository.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/src/example/tag/sqlalchemy_repository.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/src/example/tag/use_case.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/src/nene2/__init__.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/src/nene2/auth/__init__.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/src/nene2/auth/api_key.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/src/nene2/auth/bearer_token.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/src/nene2/auth/deps.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/src/nene2/auth/exceptions.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/src/nene2/auth/interfaces.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/src/nene2/auth/local_verifier.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/src/nene2/cache/__init__.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/src/nene2/cache/ttl.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/src/nene2/config/__init__.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/src/nene2/config/settings.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/src/nene2/database/__init__.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/src/nene2/database/exceptions.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/src/nene2/database/health.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/src/nene2/database/interfaces.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/src/nene2/database/sqlalchemy_executor.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/src/nene2/database/utils.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/src/nene2/http/__init__.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/src/nene2/http/etag.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/src/nene2/http/health.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/src/nene2/http/pagination.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/src/nene2/http/problem_details.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/src/nene2/log/__init__.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/src/nene2/log/setup.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/src/nene2/mcp/__init__.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/src/nene2/mcp/http_client.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/src/nene2/mcp/server.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/src/nene2/middleware/__init__.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/src/nene2/middleware/domain_exception.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/src/nene2/middleware/error_handler.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/src/nene2/middleware/request_id.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/src/nene2/middleware/request_logging.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/src/nene2/middleware/request_size_limit.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/src/nene2/middleware/security_headers.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/src/nene2/middleware/setup.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/src/nene2/middleware/throttle.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/src/nene2/py.typed +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/src/nene2/security/__init__.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/src/nene2/security/webhook.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/src/nene2/use_case/__init__.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/src/nene2/use_case/protocols.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/src/nene2/validation/__init__.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/src/nene2/validation/exceptions.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/src/scripts/__init__.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/src/scripts/export_openapi.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/tests/__init__.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/tests/conftest.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/tests/example/__init__.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/tests/example/comment/__init__.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/tests/example/comment/test_comment_http.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/tests/example/comment/test_comment_repository.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/tests/example/comment/test_comment_use_case.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/tests/example/conftest.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/tests/example/note/__init__.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/tests/example/note/test_async_note_use_case.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/tests/example/note/test_list_notes.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/tests/example/note/test_note_repository.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/tests/example/tag/__init__.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/tests/example/tag/test_tag_repository.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/tests/example/tag/test_tags.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/tests/example/test_cors.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/tests/example/test_mcp.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/tests/nene2/__init__.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/tests/nene2/auth/__init__.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/tests/nene2/auth/test_api_key.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/tests/nene2/auth/test_bearer_token.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/tests/nene2/auth/test_make_require_auth.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/tests/nene2/auth/test_token_issuer.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/tests/nene2/cache/__init__.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/tests/nene2/cache/test_ttl.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/tests/nene2/config/__init__.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/tests/nene2/config/test_settings.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/tests/nene2/database/__init__.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/tests/nene2/database/test_transaction.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/tests/nene2/database/test_utils.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/tests/nene2/http/__init__.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/tests/nene2/http/test_etag.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/tests/nene2/http/test_health.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/tests/nene2/http/test_pagination.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/tests/nene2/http/test_problem_details.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/tests/nene2/log/__init__.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/tests/nene2/log/test_setup.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/tests/nene2/mcp/__init__.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/tests/nene2/mcp/test_http_client.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/tests/nene2/mcp/test_server.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/tests/nene2/middleware/__init__.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/tests/nene2/middleware/test_error_handler.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/tests/nene2/middleware/test_request_id.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/tests/nene2/middleware/test_request_logging.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/tests/nene2/middleware/test_request_size_limit.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/tests/nene2/middleware/test_security_headers.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/tests/nene2/middleware/test_setup_middlewares.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/tests/nene2/middleware/test_simple_domain_handler.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/tests/nene2/middleware/test_throttle.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/tests/nene2/security/__init__.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/tests/nene2/security/test_webhook.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/tests/nene2/use_case/__init__.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/tests/nene2/use_case/test_protocols.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/tests/nene2/use_case/test_run_in_threadpool.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/tests/nene2/validation/__init__.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/tests/nene2/validation/test_exceptions.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/tests/scripts/__init__.py +0 -0
- {nene2_python-1.8.42 → nene2_python-1.8.43}/tests/scripts/test_export_openapi.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nene2-python
|
|
3
|
-
Version: 1.8.
|
|
3
|
+
Version: 1.8.43
|
|
4
4
|
Summary: NENE2 Python — minimal API framework following NENE2's design philosophy
|
|
5
5
|
Project-URL: Homepage, https://github.com/hideyukiMORI/nene2-python
|
|
6
6
|
Project-URL: Repository, https://github.com/hideyukiMORI/nene2-python
|
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
# FT172: dataclasses モジュール
|
|
2
|
+
|
|
3
|
+
**日付**: 2026-05-21
|
|
4
|
+
**テーマ**: `dataclasses` モジュール — `dataclass`・`field()`・`__post_init__`・`asdict`・`frozen=True`/`slots=True`
|
|
5
|
+
**セキュリティ診断**: なし(172 % 3 = 1)
|
|
6
|
+
**クラッカーペンテスト**: **あり**(FT172, 176, 180... の4回毎ペンテストサイクル開始)
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 概要
|
|
11
|
+
|
|
12
|
+
Python 標準ライブラリの `dataclasses` モジュールを nene2-python フレームワーク上で検証した。
|
|
13
|
+
`dataclasses` は CLAUDE.md で `dataclass(frozen=True, slots=True)` が
|
|
14
|
+
「イミュータブル値オブジェクト」のパターンとして明示されており、
|
|
15
|
+
nene2 の Entity / ValueObject 実装の中核。
|
|
16
|
+
FT172 は4回毎クラッカーペンテストの最初のサイクルで、
|
|
17
|
+
実際に攻撃ペイロードを送り込んでエンドポイントの耐性を確認した。
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## 実装したサンプルアプリ
|
|
22
|
+
|
|
23
|
+
**場所**: `/home/xi/docker/nene2-python-FT/ft172-dataclasses/`
|
|
24
|
+
|
|
25
|
+
### 主要機能
|
|
26
|
+
|
|
27
|
+
| クラス/関数 | 概要 |
|
|
28
|
+
|---|---|
|
|
29
|
+
| `Point` (dataclass) | 可変な2D座標。`distance_to()` メソッド付き |
|
|
30
|
+
| `NoteId` (frozen, slots) | 正の整数のみ許容する値オブジェクト。`__post_init__` で検証 |
|
|
31
|
+
| `Money` (frozen, slots) | amount (≥0) + currency (3文字)。`add()` で同通貨のみ加算 |
|
|
32
|
+
| `Note` (frozen, slots) | title (非空・200文字以下) + content + tags (tuple) + priority (1-5) |
|
|
33
|
+
| `Tag` | `field(default_factory=list)` で独立したリストを各インスタンスに持つ |
|
|
34
|
+
| `NoteEntity` | `Entity` を継承し `__post_init__` で `super().__post_init__()` を呼ぶ |
|
|
35
|
+
| `UniqueResource` | `eq=False` でカスタム等値比較。`_id` フィールドを `init=False` で生成 |
|
|
36
|
+
| `asdict()` / `astuple()` / `replace()` | 変換・イミュータブルコピー |
|
|
37
|
+
| `fields()` / `get_field_metadata()` | リフレクション |
|
|
38
|
+
|
|
39
|
+
### HTTP エンドポイント
|
|
40
|
+
|
|
41
|
+
| メソッド | パス | 概要 |
|
|
42
|
+
|---|---|---|
|
|
43
|
+
| POST | `/dataclasses/point` | Point 間の距離計算 |
|
|
44
|
+
| POST | `/dataclasses/money` | Money バリューオブジェクト生成 |
|
|
45
|
+
| POST | `/dataclasses/money/add` | 2 つの Money を加算 |
|
|
46
|
+
| POST | `/dataclasses/note` | Note 作成(__post_init__ バリデーション) |
|
|
47
|
+
| POST | `/dataclasses/note/update-title` | replace() で frozen Note を更新 |
|
|
48
|
+
| GET | `/dataclasses/note/tuple` | astuple() での変換 |
|
|
49
|
+
| POST | `/dataclasses/tag` | Tag 作成(default_factory 確認) |
|
|
50
|
+
| GET | `/dataclasses/note-id` | NoteId バリューオブジェクト生成 |
|
|
51
|
+
| GET | `/dataclasses/fields/note` | fields() リフレクション |
|
|
52
|
+
| POST | `/dataclasses/entity` | NoteEntity 継承確認 |
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## テスト結果
|
|
57
|
+
|
|
58
|
+
**35 passed(摩擦ゼロ)**
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
35 passed in 0.82s
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## 摩擦ポイント
|
|
67
|
+
|
|
68
|
+
**今回の FT では実装上の摩擦はゼロだった。**
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## 観察点
|
|
73
|
+
|
|
74
|
+
### 観察1: `frozen=True, slots=True` の組み合わせが nene2 の標準値オブジェクトパターン
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
@dataclass(frozen=True, slots=True)
|
|
78
|
+
class Money:
|
|
79
|
+
amount: int
|
|
80
|
+
currency: str = "JPY"
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
`frozen=True` でハッシュ可能・辞書キー使用可能・誤った変更を防ぐ。
|
|
84
|
+
`slots=True` でメモリ効率化(`__dict__` なし)。
|
|
85
|
+
両方セットが nene2 の推奨。`__post_init__` でビジネスルールを強制できる。
|
|
86
|
+
|
|
87
|
+
### 観察2: `field(default_factory=list)` は各インスタンスに独立したリストを作る
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
@dataclass
|
|
91
|
+
class Tag:
|
|
92
|
+
aliases: list[str] = field(default_factory=list)
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
`aliases: list[str] = []` と書くとすべてのインスタンスが同じリストを共有してしまう
|
|
96
|
+
(Python の有名な罠)。`field(default_factory=list)` が正しいパターン。
|
|
97
|
+
`frozen=True` な dataclass では `tuple` を使うことでこの問題を回避できる。
|
|
98
|
+
|
|
99
|
+
### 観察3: `replace()` で frozen dataclass を「コピーして一部変更」できる
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
updated = replace(note, title="New Title")
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
`frozen=True` は直接代入を禁止するが `replace()` で新しいインスタンスを生成できる。
|
|
106
|
+
UseCase 内でドメインオブジェクトを更新するパターンに使える。
|
|
107
|
+
|
|
108
|
+
### 観察4: 継承 dataclass では `super().__post_init__()` を明示的に呼ぶ
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
@dataclass(frozen=True, slots=True)
|
|
112
|
+
class NoteEntity(Entity):
|
|
113
|
+
def __post_init__(self) -> None:
|
|
114
|
+
super().__post_init__() # Entity の検証を実行
|
|
115
|
+
if not self.title.strip():
|
|
116
|
+
raise ValueError("title cannot be empty")
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Python の dataclass 継承では `__post_init__` の呼び出しチェーンは自動化されない。
|
|
120
|
+
`super().__post_init__()` を書かないと親クラスのバリデーションがスキップされる。
|
|
121
|
+
ruff / mypy ではこのミスを検出できないため、コードレビューチェックポイントが必要。
|
|
122
|
+
|
|
123
|
+
### 観察5: `field(metadata=...)` でフィールドにメタデータを付与できる
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
priority: int = field(default=1, metadata={"min": 1, "max": 5})
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
`metadata` はイミュータブルな `MappingProxyType` として保存される。
|
|
130
|
+
`fields(Note)` で取得でき、バリデーション設定・OpenAPI スキーマ生成・デバッグツールに活用できる。
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## nene2-python フレームワークとの統合
|
|
135
|
+
|
|
136
|
+
- `Note`, `Tag`, `Comment` エンティティはすべて `dataclass(frozen=True, slots=True)` で実装すべき(現状の実装と一致)
|
|
137
|
+
- `__post_init__` のビジネスルールは UseCase の入力検証と分離 — ドメイン不変条件は Entity が責任を持つ
|
|
138
|
+
- `replace()` パターンは UseCase の「更新」操作を副作用なしに実装するための標準手法
|
|
139
|
+
- `asdict()` は JSON レスポンス・DB 保存の前処理として使える(ただし nested dataclass も展開される点に注意)
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## Developer Experience (DX) Review
|
|
144
|
+
|
|
145
|
+
### ペルソナ1: 初心者(Python 歴1年・独学中・女性・バックエンド志望)
|
|
146
|
+
|
|
147
|
+
`@dataclass` は `class` の boilerplate(`__init__`・`__repr__`・`__eq__`)を自動化する機能として直感的に理解できる。
|
|
148
|
+
`frozen=True` の「変更できない」制約は最初は不便に感じるが、イミュータブリティの価値を説明すると受け入れる。
|
|
149
|
+
|
|
150
|
+
**ドキュメント理解**: `field(default_factory=list)` が必要な理由(同一リスト共有の罠)は
|
|
151
|
+
一度ハマらないと気づかない。nene2 の examples でこのパターンを明示すると良い。
|
|
152
|
+
|
|
153
|
+
**事故リスク**: 中。`frozen=True` なしの dataclass にグローバルから書き込むと副作用が発生する。
|
|
154
|
+
nene2 の「すべて frozen」規約が守られていれば事故率は低い。
|
|
155
|
+
|
|
156
|
+
**規約の使いやすさ**: `@dataclass(frozen=True, slots=True)` + `__post_init__` のパターンはコピペで使える。
|
|
157
|
+
|
|
158
|
+
### ペルソナ2: ロースキル経験者(Python 歴3-4年・スクリプト系・男性・SES)
|
|
159
|
+
|
|
160
|
+
`replace()` を知らずに `note.title = "new"` を試みて `FrozenInstanceError` で止まる。
|
|
161
|
+
エラーメッセージから解決策を探すが `replace()` にたどり着くのに時間がかかる。
|
|
162
|
+
|
|
163
|
+
**コピペ可能性**: 中。`replace()` は知らなければ思いつかない。ドキュメントが必要。
|
|
164
|
+
|
|
165
|
+
**拡張時の罠**: 継承 dataclass で `super().__post_init__()` を忘れる。
|
|
166
|
+
親クラスの検証がスキップされることに気づかず本番に出る可能性。
|
|
167
|
+
|
|
168
|
+
**セキュリティ的な事故リスク**: 中(ペンテストで発見した float overflow 参照)。
|
|
169
|
+
|
|
170
|
+
### ペルソナ3: フロントエンド寄り経験者(React/TS 歴4年・バックエンド転向中・ノンバイナリ)
|
|
171
|
+
|
|
172
|
+
TypeScript の `readonly` interface と `frozen dataclass` が概念的に対応する。
|
|
173
|
+
`asdict()` は TypeScript の `toJSON()` に相当。
|
|
174
|
+
|
|
175
|
+
**事故リスク**: 低。概念の対応関係が作れる。
|
|
176
|
+
|
|
177
|
+
### ペルソナ4: バックエンド経験者(Django/FastAPI 歴5-6年・男性・リードエンジニア)
|
|
178
|
+
|
|
179
|
+
Django の `Model` クラスと nene2 の `dataclass` は役割が異なる。
|
|
180
|
+
Django Model は DB 操作まで含むが、nene2 の dataclass は純粋なドメインオブジェクト。
|
|
181
|
+
リポジトリパターンで分離されているため、モデル変更が DB に直結しない設計が明確。
|
|
182
|
+
|
|
183
|
+
**本番投入可能性**: 問題なし。ただし float フィールドの `nan`/`inf` 対策が必要(ペンテスト結果参照)。
|
|
184
|
+
|
|
185
|
+
### ペルソナ5: シニアエンジニア(設計・コードレビュー担当・女性・10-12年)
|
|
186
|
+
|
|
187
|
+
**コードレビューチェックポイント**:
|
|
188
|
+
- [ ] 継承 dataclass で `super().__post_init__()` を呼んでいるか
|
|
189
|
+
- [ ] `field(default=list)` でなく `field(default_factory=list)` を使っているか
|
|
190
|
+
- [ ] `float` フィールドに `nan`/`inf` が入った場合の動作を確認しているか(ペンテスト E4 参照)
|
|
191
|
+
- [ ] `asdict()` を JSON レスポンスに使う場合、nested dataclass が再帰的に展開されることを理解しているか
|
|
192
|
+
- [ ] `__post_init__` のバリデーションエラーが API 側で適切にハンドリングされているか
|
|
193
|
+
|
|
194
|
+
**チームでの安全なパターン**: `float` フィールドを含む dataclass は Pydantic の `Field(allow_inf_nan=False)` または
|
|
195
|
+
`__post_init__` で `math.isfinite()` チェックを追加する規約を設ける。
|
|
196
|
+
|
|
197
|
+
### ペルソナ6: 設計者・ポリシー照合(nene2-python 設計ポリシー目線)
|
|
198
|
+
|
|
199
|
+
**ポリシー達成度**: 高
|
|
200
|
+
|
|
201
|
+
**「初心者でも安全な API」達成度**: 中
|
|
202
|
+
- `__post_init__` バリデーションは HTTP 境界の Pydantic より後で実行される二重チェックで安全
|
|
203
|
+
- ただし float フィールドの `nan`/`inf` は両方の層をすり抜ける(ペンテスト参照)
|
|
204
|
+
|
|
205
|
+
**設計上の負債**:
|
|
206
|
+
- Pydantic の `float` フィールドは `nan`/`inf` を許容するため、`math.isfinite()` チェックが必要
|
|
207
|
+
- CLAUDE.md に「float フィールドには `math.isfinite()` 確認必須」の規約が未記載
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
## クラッカーペンテスト(FT172 — 4回毎の第1回)
|
|
212
|
+
|
|
213
|
+
> **実施方針**: チェックリストではなく、実際に攻撃ペイロードを送り込んで耐えられるかを試験する。
|
|
214
|
+
> クラッカーは公開 API の仕様から内部構造を推測し、想定外の入力で動作を崩そうとする。
|
|
215
|
+
|
|
216
|
+
### フェーズ1: 構造推測(攻撃者の視点)
|
|
217
|
+
|
|
218
|
+
`GET /openapi.json` から推測できる内部構造:
|
|
219
|
+
- `MoneyBody: {amount, currency}` → 内部に `Money(amount, currency)` の dataclass が存在する
|
|
220
|
+
- `NoteBody: {title, content, tags, priority}` → `__post_init__` でバリデーションがある可能性
|
|
221
|
+
- `amount: int, ge=0` → 0 は許容、負の数は Pydantic でブロック
|
|
222
|
+
- `currency: str, min_length=3, max_length=3` → 固定長の文字列、文字種は制限なし(推測)
|
|
223
|
+
|
|
224
|
+
エラーメッセージから漏洩した内部情報:
|
|
225
|
+
- `{"type": "greater_than_equal", "loc": ["body", "amount"]}` → Pydantic v2 の構造が判明
|
|
226
|
+
- `Note title cannot be empty` → `__post_init__` のエラーメッセージがそのまま漏洩
|
|
227
|
+
- `ValueError` の内容がそのまま `{"error": "..."}` として公開される
|
|
228
|
+
|
|
229
|
+
### フェーズ2: 攻撃実行ログ
|
|
230
|
+
|
|
231
|
+
#### A. Pydantic バイパス攻撃
|
|
232
|
+
|
|
233
|
+
| ペイロード | 結果 | 判定 |
|
|
234
|
+
|---|---|---|
|
|
235
|
+
| `amount: "1000"` (str→int) | 200 OK、`amount: 1000` に変換 | ⚠️ 型強制(設計上許容) |
|
|
236
|
+
| extra fields: `is_admin: true` | 201 OK、extra は無視 | ✅ 耐えた |
|
|
237
|
+
| `title: null` | 422 | ✅ 耐えた |
|
|
238
|
+
| `priority: 0` (ge=1 境界外) | 422 | ✅ 耐えた |
|
|
239
|
+
| `priority: 2^31` | 422 | ✅ 耐えた |
|
|
240
|
+
| `amount: 1.9` (float→int) | 422(ge=0 通過後に精度エラー) | ✅ 耐えた |
|
|
241
|
+
|
|
242
|
+
**注記**: Pydantic v2 は `"1000"` を `int` に型強制する。セキュリティ上の問題ではないが、
|
|
243
|
+
型安全を重視する場合は `ConfigDict(strict=True)` が必要。
|
|
244
|
+
|
|
245
|
+
#### B. ビジネスロジック攻撃
|
|
246
|
+
|
|
247
|
+
| ペイロード | 結果 | 判定 |
|
|
248
|
+
|---|---|---|
|
|
249
|
+
| `title: "\t\n "` (空白のみ) | 422 (`Note title cannot be empty`) | ✅ 耐えた |
|
|
250
|
+
| `currency: "JP"` (2文字) | 422 (Pydantic min_length) | ✅ 耐えた |
|
|
251
|
+
| `currency: "JPYY"` (4文字) | 422 (Pydantic max_length) | ✅ 耐えた |
|
|
252
|
+
| `currency: "<sc"` (HTML文字) | **200 OK、`<sc` がそのまま返る** | ⚠️ 弱点(後述) |
|
|
253
|
+
| `currency: "';-"` (SQL文字) | **200 OK、`';-` がそのまま返る** | ⚠️ 弱点(後述) |
|
|
254
|
+
|
|
255
|
+
**⚠️ B4 弱点: currency フィールドは文字種チェックなし**
|
|
256
|
+
3文字の長さ制限のみで、HTML/SQL の特殊文字が通過する。
|
|
257
|
+
FT172 は JSON API のため XSS は直接発生しないが、もし通貨コードがログ・メール・HTMLに埋め込まれると問題になる。
|
|
258
|
+
ISO 4217 コード(`[A-Z]{3}`)のみ許容するバリデーションが推奨。
|
|
259
|
+
|
|
260
|
+
#### C. 境界値/エッジケース攻撃
|
|
261
|
+
|
|
262
|
+
| ペイロード | 結果 | 判定 |
|
|
263
|
+
|---|---|---|
|
|
264
|
+
| `title: "hello\x00world"` (null バイト) | **201 OK、null バイトが保存される** | ⚠️ 弱点(後述) |
|
|
265
|
+
| `title: "a" * 200` (境界ちょうど) | 201 OK | ✅ 耐えた |
|
|
266
|
+
| `title: "a" * 201` (境界+1) | 422 | ✅ 耐えた |
|
|
267
|
+
| RTL オーバーライド文字 U+202E | **201 OK、そのまま保存** | ⚠️ 弱点(後述) |
|
|
268
|
+
| Unicode NFC vs NFD | 両方 201 OK(正規化なし) | ⚠️ 注意 |
|
|
269
|
+
| 21 tags (max=20) | 422 | ✅ 耐えた |
|
|
270
|
+
|
|
271
|
+
**⚠️ C1 弱点: null バイトがタイトルに保存される**
|
|
272
|
+
`"hello\x00world"` が `201 OK` で保存される。
|
|
273
|
+
Python の文字列操作では無害だが、このタイトルを C ライブラリ・ファイルパス・DBに渡すと
|
|
274
|
+
null バイト以降が切り捨てられる(null バイトインジェクション)。
|
|
275
|
+
`__post_init__` に `"\x00" in self.title` チェックが必要。
|
|
276
|
+
|
|
277
|
+
**⚠️ C4 弱点: RTL オーバーライド文字が保存される**
|
|
278
|
+
`U+202E` (RIGHT-TO-LEFT OVERRIDE) がタイトルに保存される。
|
|
279
|
+
ファイルダウンロード機能でタイトルがファイル名に使われると `safeevil.exe` が `safeexe.evil` に見える。
|
|
280
|
+
テキスト表示のみの場合は低リスクだが、ファイル名利用時は危険。
|
|
281
|
+
|
|
282
|
+
#### D. 情報収集攻撃
|
|
283
|
+
|
|
284
|
+
| ペイロード | 結果 | 判定 |
|
|
285
|
+
|---|---|---|
|
|
286
|
+
| `GET /dataclasses/admin` | 404 Not Found | ✅ 耐えた |
|
|
287
|
+
| 不正 JSON | 422 `{"detail": [...]}` | ✅ スタックトレース非公開 |
|
|
288
|
+
| `amount: -999` のエラー | `{"detail": [{"type": "greater_than_equal", ...}]}` | ⚠️ Pydantic の内部型情報が漏洩 |
|
|
289
|
+
| `GET /docs` | **200 OK** | ⚠️ 本番では無効化が必要 |
|
|
290
|
+
|
|
291
|
+
**⚠️ D3 弱点: Pydantic のエラーレスポンスに内部型情報が含まれる**
|
|
292
|
+
`"type": "greater_than_equal"` というフィールド名が漏洩する。
|
|
293
|
+
FT172 はサンドボックスのため許容するが、本番 API では Pydantic エラーを nene2 の
|
|
294
|
+
Problem Details 形式に変換してフィールド名を抽象化する必要がある。
|
|
295
|
+
|
|
296
|
+
#### E. DoS 試み
|
|
297
|
+
|
|
298
|
+
| ペイロード | 結果 | 判定 |
|
|
299
|
+
|---|---|---|
|
|
300
|
+
| 1000 tags (max=20) | 422 | ✅ 耐えた |
|
|
301
|
+
| content=10000 chars (max=5000) | 422 | ✅ 耐えた |
|
|
302
|
+
| 1000 aliases (max=10) | 422 | ✅ 耐えた |
|
|
303
|
+
| **`x: Infinity` の raw JSON** | **500 Internal Server Error** | ❌ **突破!** |
|
|
304
|
+
| `x: 1e308` | **500 Internal Server Error** | ❌ **突破!** |
|
|
305
|
+
|
|
306
|
+
**❌ E4 重大発見: float オーバーフローで 500 が返る(DoS 脆弱性)**
|
|
307
|
+
|
|
308
|
+
```
|
|
309
|
+
POST /dataclasses/point {"x": 1e308, "y": 0}
|
|
310
|
+
→ OverflowError: Numerical result out of range
|
|
311
|
+
→ 500 Internal Server Error
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
攻撃手順:
|
|
315
|
+
1. Pydantic の `float` フィールドは `1e308` を受け入れる(有効な float)
|
|
316
|
+
2. `distance_to()` で `(1e308 - 0) ** 2` が計算され `OverflowError` が発生
|
|
317
|
+
3. `ErrorHandlerMiddleware` が `OverflowError` を捕捉して 500 を返す
|
|
318
|
+
4. クライアントは繰り返し 500 を引き起こしてサービスを不安定化できる
|
|
319
|
+
|
|
320
|
+
**影響**: 任意のクライアントが `/dataclasses/point` に `{"x": 1e308}` を連続送信すると
|
|
321
|
+
サーバーが毎回 500 エラーを返す。直接クラッシュはしないが、ログが汚染され
|
|
322
|
+
異常検知システムが誤反応する可能性がある。
|
|
323
|
+
|
|
324
|
+
**修正方法**:
|
|
325
|
+
```python
|
|
326
|
+
# Pydantic フィールドで制限
|
|
327
|
+
x: float = Field(gt=-1e100, lt=1e100) # 現実的な上下限
|
|
328
|
+
|
|
329
|
+
# または __post_init__ で
|
|
330
|
+
import math
|
|
331
|
+
def __post_init__(self) -> None:
|
|
332
|
+
if not (math.isfinite(self.x) and math.isfinite(self.y)):
|
|
333
|
+
raise ValueError("Coordinates must be finite")
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
### フェーズ3: 攻撃まとめ
|
|
337
|
+
|
|
338
|
+
| 攻撃カテゴリ | 試みた攻撃数 | 突破 | 耐えた | 弱点発見 |
|
|
339
|
+
|---|---|---|---|---|
|
|
340
|
+
| Pydantic バイパス | 6 | 0 | 6 | 型強制(許容) |
|
|
341
|
+
| ビジネスロジック | 5 | 0 | 3 | 2(特殊文字) |
|
|
342
|
+
| 境界値/エッジ | 7 | 0 | 4 | 3(null/RTL/Unicode) |
|
|
343
|
+
| 情報収集 | 4 | 0 | 3 | 1(Pydantic 型情報) |
|
|
344
|
+
| DoS | 5 | **2** | 3 | **float overflow → 500** |
|
|
345
|
+
|
|
346
|
+
**攻撃耐性評価**: 軽微な問題あり(float overflow は要修正)
|
|
347
|
+
**発見した弱点**:
|
|
348
|
+
1. **HIGH**: `float` フィールドへの `inf`/`1e308` で `OverflowError` → 500(DoS)
|
|
349
|
+
2. **MEDIUM**: currency フィールドの文字種チェックなし(HTML/SQL 特殊文字が通過)
|
|
350
|
+
3. **LOW**: null バイトがタイトルに保存可能
|
|
351
|
+
4. **LOW**: RTL オーバーライド文字がタイトルに保存可能
|
|
352
|
+
5. **INFO**: Pydantic のエラーレスポンスに内部型情報が含まれる
|
|
353
|
+
6. **INFO**: `/docs` が本番相当の環境でも公開されている
|
|
354
|
+
|
|
355
|
+
---
|
|
356
|
+
|
|
357
|
+
## Follow-up Issues
|
|
358
|
+
|
|
359
|
+
| 優先度 | タイトル | 種別 |
|
|
360
|
+
|---|---|---|
|
|
361
|
+
| 高 | `fix: float フィールドに math.isfinite() ガードを追加(OverflowError DoS 対策)` | fix |
|
|
362
|
+
| 高 | `docs: CLAUDE.md に float フィールドのバリデーション要件(isfinite チェック)を追記` | docs |
|
|
363
|
+
| 中 | `fix: currency フィールドに ISO 4217 形式([A-Z]{3})バリデーションを追加` | fix |
|
|
364
|
+
| 中 | `docs: null バイト・RTL 文字の __post_init__ チェックパターンを how-to に追加` | docs |
|
|
365
|
+
| 低 | `feat: Pydantic ValidationError を nene2 Problem Details に変換して型情報を抽象化` | feat |
|
|
366
|
+
|
|
367
|
+
---
|
|
368
|
+
|
|
369
|
+
## まとめ
|
|
370
|
+
|
|
371
|
+
`dataclasses` モジュールは nene2-python の Entity / ValueObject 設計に直結する重要な機能。
|
|
372
|
+
35 テスト全通過、摩擦ゼロ。
|
|
373
|
+
|
|
374
|
+
**クラッカーペンテストの主な発見**: `float` フィールドへの `1e308` / `Infinity` 送信で
|
|
375
|
+
`OverflowError` が発生し 500 が返る(DoS 脆弱性)。
|
|
376
|
+
Pydantic の `float` 型は `inf`・`nan`・`1e308` を有効な値として受け入れるが、
|
|
377
|
+
演算時に `OverflowError` が発生する。
|
|
378
|
+
`math.isfinite()` または `Field(gt=-1e100, lt=1e100)` による対策が必要。
|
|
379
|
+
|
|
380
|
+
通貨コードの文字種チェック不足・null バイト通過・RTL オーバーライド文字の保存は
|
|
381
|
+
今後の how-to で対応パターンを記録する。
|
|
382
|
+
|
|
@@ -434,6 +434,68 @@ for v in vulns:
|
|
|
434
434
|
|
|
435
435
|
---
|
|
436
436
|
|
|
437
|
+
## クラッカーペンテスト(FT172, 176, 180... のみ実施)
|
|
438
|
+
|
|
439
|
+
> **実施方針**: チェックリストではなく、実際に攻撃ペイロードを送り込んで耐えられるかを試験する。
|
|
440
|
+
> クラッカーは公開 API の仕様から内部構造を推測し、想定外の入力で動作を崩そうとする。
|
|
441
|
+
> 「正常系しかテストしていないコード」に発見があるかを確認する。
|
|
442
|
+
|
|
443
|
+
### フェーズ1: 構造推測(攻撃者の視点)
|
|
444
|
+
|
|
445
|
+
- **公開情報から推測できる内部構造**:
|
|
446
|
+
- [OpenAPI スキーマから推測したモデル構造・DB 構造]
|
|
447
|
+
- [エラーメッセージから漏洩する実装詳細]
|
|
448
|
+
- [レスポンスタイミングから推測できる処理]
|
|
449
|
+
|
|
450
|
+
### フェーズ2: 攻撃実行ログ
|
|
451
|
+
|
|
452
|
+
各攻撃について: 試みたペイロード → 実際のレスポンス → 判定(耐えた / 突破 / 予期しない動作)
|
|
453
|
+
|
|
454
|
+
#### A. Pydantic バイパス攻撃
|
|
455
|
+
```
|
|
456
|
+
# 型強制でバリデーションを回避する試み
|
|
457
|
+
```
|
|
458
|
+
**結果**:
|
|
459
|
+
|
|
460
|
+
#### B. ビジネスロジック攻撃(ステート破壊)
|
|
461
|
+
```
|
|
462
|
+
# 状態遷移の不正操作・競合状態の悪用
|
|
463
|
+
```
|
|
464
|
+
**結果**:
|
|
465
|
+
|
|
466
|
+
#### C. 境界値・エッジケース攻撃
|
|
467
|
+
```
|
|
468
|
+
# 上限/下限の境界・null・空文字・Unicode の悪用
|
|
469
|
+
```
|
|
470
|
+
**結果**:
|
|
471
|
+
|
|
472
|
+
#### D. 情報収集攻撃(エラーメッセージ解析)
|
|
473
|
+
```
|
|
474
|
+
# 意図的にエラーを発生させて内部情報を抽出
|
|
475
|
+
```
|
|
476
|
+
**結果**:
|
|
477
|
+
|
|
478
|
+
#### E. DoS 試み
|
|
479
|
+
```
|
|
480
|
+
# CPU/メモリを枯渇させる入力パターン
|
|
481
|
+
```
|
|
482
|
+
**結果**:
|
|
483
|
+
|
|
484
|
+
### フェーズ3: 攻撃まとめ
|
|
485
|
+
|
|
486
|
+
| 攻撃カテゴリ | 試みた攻撃数 | 突破 | 耐えた | 予期しない動作 |
|
|
487
|
+
|---|---|---|---|---|
|
|
488
|
+
| Pydantic バイパス | N | 0 | N | 0 |
|
|
489
|
+
| ビジネスロジック | N | 0 | N | 0 |
|
|
490
|
+
| 境界値/エッジ | N | 0 | N | 0 |
|
|
491
|
+
| 情報収集 | N | 0 | N | 0 |
|
|
492
|
+
| DoS | N | 0 | N | 0 |
|
|
493
|
+
|
|
494
|
+
**攻撃耐性評価**: 堅牢 / 軽微な問題あり / 要修正
|
|
495
|
+
**発見した弱点**: [具体的な改善点]
|
|
496
|
+
|
|
497
|
+
---
|
|
498
|
+
|
|
437
499
|
## Follow-up Issues
|
|
438
500
|
|
|
439
501
|
| 優先度 | タイトル | 種別 |
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{nene2_python-1.8.42 → nene2_python-1.8.43}/alembic/versions/001_create_notes_and_tags_tables.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|