capauth 0.3.0__tar.gz → 0.3.2__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.
- {capauth-0.3.0 → capauth-0.3.2}/CHANGELOG.md +176 -0
- {capauth-0.3.0 → capauth-0.3.2}/PKG-INFO +1 -1
- {capauth-0.3.0 → capauth-0.3.2}/SOP.md +23 -1
- capauth-0.3.2/docs/TRUST_GRAPH_COORD_PROJECTION.md +201 -0
- capauth-0.3.2/ruff-0.15.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/__init__.py +18 -1
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/pairing/__init__.py +15 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/pairing/kernel.py +42 -9
- capauth-0.3.2/src/capauth/pairing/proof.py +455 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/pairing/store.py +57 -2
- capauth-0.3.2/src/capauth/testing.py +324 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/trust/__init__.py +2 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/trust/graph.py +258 -23
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth.egg-info/PKG-INFO +1 -1
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth.egg-info/SOURCES.txt +10 -0
- {capauth-0.3.0 → capauth-0.3.2}/tests/conftest.py +67 -67
- capauth-0.3.2/tests/test_device_key_proof.py +243 -0
- capauth-0.3.2/tests/test_enrollment_proof_helper.py +432 -0
- {capauth-0.3.0 → capauth-0.3.2}/tests/test_identity_class.py +28 -3
- capauth-0.3.2/tests/test_pairing_base_dir_env.py +122 -0
- capauth-0.3.2/tests/test_testing_helper.py +536 -0
- capauth-0.3.2/tests/test_trust_graph_coord_audit.py +292 -0
- capauth-0.3.2/tests/test_version_honesty.py +61 -0
- {capauth-0.3.0 → capauth-0.3.2}/.cursorrules +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/.dockerignore +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/.github/workflows/ci.yml +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/.github/workflows/docs-check.yml +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/.github/workflows/nextcloud-appstore.yml +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/.github/workflows/publish.yml +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/.github/workflows/pytest.yml +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/.github/workflows/secret-scan.yml +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/.gitignore +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/.gitleaks-baseline.json +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/AGENTS.md +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/AI-ADVOCATE.md +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/ARCHITECTURE.md +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/CLAUDE.md +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/CODE_OF_CONDUCT.md +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/CONTRIBUTING.md +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/Dockerfile +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/Dockerfile.authentik-capauth +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/LICENSE +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/MANIFEST.in +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/MISSION.md +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/README.md +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/SECURITY.md +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/SKILL.md +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/authentik-custom/capauth_migrate.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/authentik-custom/imghdr.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/authentik-custom/user_settings.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/bin/cli.js +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/browser-extension/.gitignore +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/browser-extension/README.md +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/browser-extension/RELEASING.md +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/browser-extension/background.js +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/browser-extension/content_scripts/autofill.js +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/browser-extension/content_scripts/cross_origin_signing.js +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/browser-extension/content_scripts/detector.js +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/browser-extension/content_scripts/provider.js +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/browser-extension/content_scripts/provider_bridge.js +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/browser-extension/icons/icon.svg +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/browser-extension/icons/icon128.png +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/browser-extension/icons/icon16.png +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/browser-extension/icons/icon48.png +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/browser-extension/lib/bunker-e2e.js +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/browser-extension/lib/keyqr.js +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/browser-extension/lib/keyvault.js +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/browser-extension/lib/openpgp.js +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/browser-extension/lib/signer-backends.js +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/browser-extension/manifest.firefox.json +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/browser-extension/manifest.json +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/browser-extension/native-host/capauth_signer.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/browser-extension/native-host/com.capauth.signer.json +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/browser-extension/native-host/install.sh +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/browser-extension/options/options.html +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/browser-extension/options/options.js +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/browser-extension/package-lock.json +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/browser-extension/package.json +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/browser-extension/popup/popup.css +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/browser-extension/popup/popup.html +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/browser-extension/popup/popup.js +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/browser-extension/scripts/build.js +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/browser-extension/scripts/generate-icons.js +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/browser-extension/stages/AuthenticatorSelectionStage.js +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/browser-extension/stages/CapAuthStage.js +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/browser-extension/stages/QRDisplayStage.js +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/browser-extension/stages/SignatureVerificationStage.js +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/browser-extension/stages/StagePipeline.js +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/browser-extension/submission/STORE_LISTING.md +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/browser-extension/submission/privacy-policy.md +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/browser-extension/tests/fixtures/test_key.json +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/browser-extension/tests/unit/background.test.js +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/browser-extension/tests/unit/bunker_e2e.test.js +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/browser-extension/tests/unit/detector.test.js +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/browser-extension/tests/unit/keyqr.test.js +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/browser-extension/tests/unit/keyvault.test.js +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/browser-extension/tests/unit/openpgp.test.js +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/browser-extension/tests/unit/provider_api.test.js +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/browser-extension/tests/unit/provider_tierb.test.js +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/browser-extension/tests/unit/remote_bunker.test.js +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/browser-extension/tests/unit/signer_backends.test.js +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/browser-extension/vitest.config.js +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/deploy/capauth-service/.env.example +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/deploy/capauth-service/deploy.sh +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/deploy/capauth-service/docker-compose.yml +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/deploy/capauth-service/systemd/capauth-backup.service +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/deploy/capauth-service/systemd/capauth-backup.timer +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/deploy/forgejo-capauth/.env.example +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/deploy/forgejo-capauth/docker-compose.yml +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/deploy/forgejo-capauth/preflight.sh +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/docs/AIRGAPPED_SIGNING_WORKSTATION.md +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/docs/ARCHITECTURE.md +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/docs/AUTHENTIK_CUSTOM_STAGE.md +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/docs/AUTHENTIK_DEPLOYMENT_SKSSO.md +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/docs/AUTHENTIK_FORGEJO_DEPLOYMENT.md +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/docs/AUTHENTIK_STRATEGY_DECISION.md +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/docs/CAPAUTH_BUNKER_REMOTE_SIGNER.md +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/docs/CAPAUTH_OIDC_IDP.md +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/docs/CLAIMS.md +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/docs/COLD_MACHINE_BOOTSTRAP_AND_DR.md +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/docs/CRYPTO_SPEC.md +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/docs/ENTERPRISE_MANAGED_KEYS.md +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/docs/INTEGRATION_BLUEPRINT.md +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/docs/M0-capauth-api-v1.md +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/docs/M0-capauth-importer-inventory.md +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/docs/PQC_CAPAUTH_REVISIT.md +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/docs/PQC_ROOT_MIGRATION.md +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/docs/PROTOCOL.md +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/docs/ROOT_ROTATION_CEREMONY.md +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/docs/ZERO_KNOWLEDGE_PROFILE.md +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/docs/authentik-capauth.md +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/docs/deploy-plan/capauth-bulletproof-deploy.md +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/index.d.ts +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/index.js +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/openclaw-plugin.archived-2026-04-23/package.json +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/openclaw-plugin.archived-2026-04-23/src/index.ts +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/openclaw-plugin.archived-2026-04-23/src/openclaw.plugin.json +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/package.json +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/phone-signer/app.js +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/phone-signer/icons/icon-192.png +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/phone-signer/icons/icon-512.png +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/phone-signer/index.html +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/phone-signer/lib/bunker-e2e.js +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/phone-signer/lib/bunker-signer.js +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/phone-signer/lib/canonical.js +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/phone-signer/lib/keyqr.js +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/phone-signer/lib/keyvault.js +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/phone-signer/manifest.webmanifest +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/phone-signer/sw.js +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/phone-signer/vendor/openpgp.min.js +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/phone-signer/vendor/qrcode-generator.js +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/pyproject.toml +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/scripts/build_authentik_capauth_image.sh +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/scripts/capauth-backup.sh +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/scripts/capauth-restore.sh +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/scripts/migrate_canonical_subjects.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/scripts/pqc_ceremony_dryrun.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/scripts/provision_agent_profiles.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/setup.cfg +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/skill.yaml +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/agent_identity.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/apps.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/authentik/__init__.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/authentik/api.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/authentik/blueprints/capauth-pgp-login.yaml +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/authentik/claims_mapper.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/authentik/nonce_store.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/authentik/stage.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/authentik/urls.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/authentik/verifier.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/authz.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/cli.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/crypto/__init__.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/crypto/base.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/crypto/gnupg_backend.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/crypto/pgpy_backend.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/crypto/sequoia_backend.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/crypto/skpgp_backend.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/custody.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/did.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/discovery/__init__.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/discovery/base.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/discovery/file_discovery.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/discovery/mdns.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/discovery/syncthing.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/exceptions.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/identity.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/identity_class.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/integration.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/integrations/__init__.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/integrations/forgejo/__init__.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/integrations/forgejo/auth_flow.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/integrations/forgejo/cli.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/integrations/forgejo/config.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/integrations/forgejo/forgejo_api.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/integrations/forgejo/oidc_provider.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/integrations/forgejo/tests/__init__.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/integrations/forgejo/tests/test_auth_flow.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/integrations/forgejo/tests/test_cli.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/integrations/forgejo/tests/test_config.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/integrations/forgejo/tests/test_forgejo_api.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/integrations/forgejo/tests/test_oidc_provider.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/integrations/nextcloud/.gitignore +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/integrations/nextcloud/.nextcloudignore +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/integrations/nextcloud/CSR-PR.md +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/integrations/nextcloud/INFO-XML-CHANGES.md +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/integrations/nextcloud/PUBLISHING.md +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/integrations/nextcloud/README.md +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/integrations/nextcloud/appinfo/info.xml +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/integrations/nextcloud/appinfo/routes.php +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/integrations/nextcloud/build-release.sh +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/integrations/nextcloud/certificates/.gitignore +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/integrations/nextcloud/certificates/capauth.crt +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/integrations/nextcloud/certificates/capauth.csr +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/integrations/nextcloud/composer.json +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/integrations/nextcloud/css/login.css +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/integrations/nextcloud/js/login.js +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/integrations/nextcloud/krankerl.toml +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/integrations/nextcloud/lib/AlternativeLogin/CapAuthLogin.php +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/integrations/nextcloud/lib/AppInfo/Application.php +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/integrations/nextcloud/lib/BackgroundJob/PruneExpiredNonces.php +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/integrations/nextcloud/lib/Controller/LoginController.php +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/integrations/nextcloud/lib/Controller/TokenController.php +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/integrations/nextcloud/lib/Db/KeyRegistry.php +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/integrations/nextcloud/lib/Exception/CapAuthUnauthorizedException.php +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/integrations/nextcloud/lib/Middleware/PgpVerificationMiddleware.php +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/integrations/nextcloud/lib/Migration/Version000300Date20260301000000.php +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/integrations/nextcloud/lib/Provider/CapAuthProvider.php +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/integrations/nextcloud/lib/Service/ChallengeService.php +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/integrations/nextcloud/lib/Service/GroupSyncService.php +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/integrations/nextcloud/lib/Service/UserProvisioningService.php +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/integrations/nextcloud/lib/Service/VerifierService.php +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/integrations/nextcloud/lib/User/Backend.php +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/integrations/nextcloud/phpunit.xml +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/integrations/nextcloud/screenshots/README.md +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/integrations/nextcloud/screenshots/login.png +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/integrations/nextcloud/templates/login.php +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/integrations/nextcloud/tests/bootstrap.php +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/integrations/nextcloud/tests/integration/FullAuthFlowTest.php +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/integrations/nextcloud/tests/oidc/oidc_e2e_login_test.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/integrations/nextcloud/tests/unit/Controller/LoginControllerTest.php +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/integrations/nextcloud/tests/unit/Db/KeyRegistryTest.php +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/integrations/nextcloud/tests/unit/Provider/CapAuthProviderTest.php +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/integrations/nextcloud/tests/unit/Service/ChallengeServiceTest.php +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/integrations/nextcloud/tests/unit/Service/VerifierServiceTest.php +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/integrations/nextcloud/tests/unit/User/BackendTest.php +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/key_transparency.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/login.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/manifest.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/mesh.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/migrations/0001_initial.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/migrations/0002_widen_fingerprint_to_64.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/migrations/__init__.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/models.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/pairing/canonicalize.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/pairing/operator_session.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/pairing/records.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/pairing/window.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/pma.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/pqc_confidentiality.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/pqc_identity.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/pqc_root_identity.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/pqc_tokens.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/profile.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/provisioning.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/registry.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/seal.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/service/__init__.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/service/app.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/service/bunker.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/service/bunker_e2e.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/service/keystore.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/service/oidc/__init__.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/service/oidc/clients.example.json +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/service/oidc/clients.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/service/oidc/passkey.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/service/oidc/provider.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/service/oidc/signing_key.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/service/oidc/store.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/service/push.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/service/server.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/subject.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/sync.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/tokens.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth/trust/calibration.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth.egg-info/dependency_links.txt +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth.egg-info/entry_points.txt +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth.egg-info/requires.txt +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/src/capauth.egg-info/top_level.txt +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/stages/ak-stage-capauth/.gitignore +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/stages/ak-stage-capauth/package-lock.json +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/stages/ak-stage-capauth/package.json +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/stages/ak-stage-capauth/src/CapAuthStage.test.js +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/stages/ak-stage-capauth/src/CapAuthStage.vue +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/stages/ak-stage-capauth/src/index.js +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/stages/ak-stage-capauth/vite.config.js +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/tests/__init__.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/tests/fixtures/bunker_e2e_v1_vector.json +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/tests/fixtures/canonical_nonce_v2_vector.json +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/tests/test_agent_identity.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/tests/test_agentrun_capabilities.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/tests/test_audience_mint_helper.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/tests/test_audience_tokens.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/tests/test_authz.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/tests/test_authz_legacy_grace.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/tests/test_authz_signature_gate.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/tests/test_backup_restore.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/tests/test_bunker.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/tests/test_bunker_e2e.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/tests/test_canonicalize.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/tests/test_capauth_service_deploy.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/tests/test_change_capabilities.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/tests/test_claims_mapper.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/tests/test_crypto.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/tests/test_custody.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/tests/test_did_capability_relationships.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/tests/test_fingerprint_v6.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/tests/test_forgejo_preflight.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/tests/test_home_resolution.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/tests/test_identity.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/tests/test_identity_ttl.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/tests/test_integration.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/tests/test_key_revocation_expiry.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/tests/test_key_transparency.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/tests/test_login.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/tests/test_manifest.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/tests/test_mesh.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/tests/test_models_pqc_q0.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/tests/test_nextcloud_app.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/tests/test_no_subapp_dependency.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/tests/test_nonce_store.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/tests/test_oidc_idp.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/tests/test_oidc_idp_e2e.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/tests/test_operator_session.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/tests/test_pairing.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/tests/test_passkey.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/tests/test_pma.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/tests/test_pqc_confidentiality.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/tests/test_pqc_identity.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/tests/test_pqc_root_integration.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/tests/test_pqc_t3_gate.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/tests/test_pqc_tokens.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/tests/test_profile.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/tests/test_provisioning.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/tests/test_push.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/tests/test_py313_compat.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/tests/test_qr_login.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/tests/test_real_pgp_e2e.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/tests/test_register_cli.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/tests/test_registry.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/tests/test_seal.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/tests/test_sequoia_backend.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/tests/test_service.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/tests/test_service_authz_decide.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/tests/test_service_v6_fingerprint.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/tests/test_skpgp_backend.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/tests/test_subject.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/tests/test_syncthing_discovery.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/tests/test_token_gc.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/tests/test_token_store_flag.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/tests/test_tokens.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/tests/test_trust_calibration.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/tests/test_trust_graph.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/tests/test_verifier.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/tests/test_zk_profile.py +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/tools/build-sq.sh +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/urllib.error +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/web/README.md +0 -0
- {capauth-0.3.0 → capauth-0.3.2}/web/stages/capauth/CapAuthStage.ts +0 -0
|
@@ -6,8 +6,184 @@ All notable changes to `capauth` are documented here. The format is based on
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **`capauth.pairing.proof`: a supported way to BUILD the enrollment proof card
|
|
12
|
+
N10 made mandatory.** `83c1fa2` made `proof` required for `verified` and
|
|
13
|
+
`operator_pubkey` + `attestation` required for `attested`, but shipped the
|
|
14
|
+
requirement with no supported constructor. A downstream had to reconstruct the
|
|
15
|
+
challenge from capauth's source (derive the fingerprint with
|
|
16
|
+
`fingerprint_for`, canonicalize the subject with `canonical_subject`, assemble
|
|
17
|
+
`verified_challenge` over exactly those two) and get all three right, or emit a
|
|
18
|
+
signature that fails identically to a forged one. skchat did it by hand, with a
|
|
19
|
+
comment saying "capauth has no public helper for this today; if it grows one,
|
|
20
|
+
delete this and call it."
|
|
21
|
+
- `build_verified_proof(pubkey, *, private_key, passphrase="", subject=None)` —
|
|
22
|
+
signs the `verified` challenge with the device's OWN key.
|
|
23
|
+
- `build_attested_proof(pubkey, *, operator_pubkey, operator_private_key,
|
|
24
|
+
passphrase="", subject=None)` — an operator vouching for a device it does not
|
|
25
|
+
hold the private key of.
|
|
26
|
+
- `enrollment_challenge(pubkey, *, subject=None, mode="verified")` — the
|
|
27
|
+
challenge bytes derived from the PUBLIC key alone. No secret is involved and
|
|
28
|
+
none is disclosed, so a server can hand these to a client whose WebCrypto
|
|
29
|
+
device key is non-extractable and let it sign them.
|
|
30
|
+
- `EnrollmentProof` — frozen, and implements the mapping protocol, so it
|
|
31
|
+
splats into `enroll_device` with no massaging:
|
|
32
|
+
`enroll_device(pub, scopes, mode="verified", subject=subj, **proof)`. It
|
|
33
|
+
yields only the evidence kwargs the claimed mode requires, never both sets.
|
|
34
|
+
- `ProofSigningError(PairingError)` — signing failure RAISES, in the same
|
|
35
|
+
spirit as `TokenSigningError`. An unsigned or empty proof is never returned.
|
|
36
|
+
- **It is a constructor, not a bypass.** The verifier is untouched, and is in
|
|
37
|
+
fact called on this module's own output before that output is returned (the
|
|
38
|
+
round trip is a structural property, not a hope). Every function signs with a
|
|
39
|
+
private key the CALLER supplies: presenting a proof for a key you do not
|
|
40
|
+
control is impossible by construction, because the signature IS the proof.
|
|
41
|
+
The signing branch is chosen from the device public key alone, using the same
|
|
42
|
+
`"BEGIN PGP" in key` discriminator `_proof_verifies` uses, so a caller can
|
|
43
|
+
never pick a weaker verifier for itself. Both PGP-armored keys and skchat's
|
|
44
|
+
base64 DER SPKI ECDSA P-256 device keys are supported.
|
|
45
|
+
- Negative controls in `tests/test_enrollment_proof_helper.py` pin that a proof
|
|
46
|
+
over the wrong subject, over the wrong fingerprint, signed by a different
|
|
47
|
+
key, replayed across the verified/attested domain separation, or simply
|
|
48
|
+
absent is STILL refused by the real `enroll_device`. They were verified to
|
|
49
|
+
catch a real regression by deliberately loosening the check and observing
|
|
50
|
+
them go red.
|
|
51
|
+
|
|
52
|
+
- **`$SKCAPSTONE_HOME` now overrides `capauth.pairing.store.default_base_dir()`.**
|
|
53
|
+
The root was hardcoded to `~/.skcapstone` with no override, so a node that
|
|
54
|
+
legitimately holds a per-agent key but whose flat
|
|
55
|
+
`~/.skcapstone/identity/identity.json` declares a different key had no
|
|
56
|
+
supported way to point capauth at a home it can actually sign from. Precedence
|
|
57
|
+
matches the `capauth.manifest.shell_home` idiom already in this repo: explicit
|
|
58
|
+
`base_dir=` (unchanged, still wins), then `$SKCAPSTONE_HOME` when set and
|
|
59
|
+
non-empty, then `~/.skcapstone`. An empty or whitespace-only value falls back
|
|
60
|
+
to the default rather than resolving against the cwd. Exported as
|
|
61
|
+
`capauth.pairing.SKCAPSTONE_HOME_ENV`.
|
|
62
|
+
- With the variable unset, behaviour is unchanged in every respect.
|
|
63
|
+
- ⚠ **Setting it moves the PAIRING STORE, not just the identity.** The same
|
|
64
|
+
function locates the peer registry and its device sidecars, pending
|
|
65
|
+
enrollments, identity-class assignments, and the capability-token store
|
|
66
|
+
`decide()` reads. A node pointed at a new home starts with an EMPTY device
|
|
67
|
+
store and will deny every subject enrolled in the old one for "no enrolled
|
|
68
|
+
device" until it is re-enrolled there. Nothing migrates, merges, or falls
|
|
69
|
+
back to the previous location: whether two homes should be reconciled is a
|
|
70
|
+
separate decision, and a silent merge is how one node's device population
|
|
71
|
+
quietly becomes another's.
|
|
72
|
+
|
|
73
|
+
- **`capauth.testing`: the gpg signing test seam, promoted from `tests/conftest.py`
|
|
74
|
+
into shipped, importable code.** Since `0d412ab`, `issue_token` and
|
|
75
|
+
`mint_audience_token` RAISE rather than storing an unsigned token, and
|
|
76
|
+
`decide()` rejects unsigned tokens. Both are correct, but they made real gpg (a
|
|
77
|
+
secret key present, an agent unlocked) a hard dependency of any test that
|
|
78
|
+
expects an ALLOW. A GitHub Actions runner has neither, so downstream suites went
|
|
79
|
+
red at the mint or 403'd at the gate. CapAuth's own suite was insulated only
|
|
80
|
+
because the stub was private to its `tests/conftest.py`, where no consumer could
|
|
81
|
+
reach it. It is now shipped:
|
|
82
|
+
- `capauth.testing.capauth_signing_stub` — autouse fixture; a consuming repo
|
|
83
|
+
turns it on for its whole suite with one line in its `tests/conftest.py`:
|
|
84
|
+
`from capauth.testing import capauth_signing_stub # noqa: F401`.
|
|
85
|
+
- `capauth.testing.stub_token_signing` — the same thing, non-autouse, for
|
|
86
|
+
opting in per test or per module.
|
|
87
|
+
- `capauth.testing.signing_stub()` — a plain context manager, for use outside
|
|
88
|
+
pytest.
|
|
89
|
+
- `capauth.testing.install_signing_stub(monkeypatch)` — the building block, for
|
|
90
|
+
applying the seam at a point of the caller's own choosing.
|
|
91
|
+
- The seam stubs the **gpg subprocess boundary only**: three attributes of
|
|
92
|
+
`capauth.tokens` (`_get_issuer_fingerprint`, `_pgp_sign_payload`, and the
|
|
93
|
+
`verify_manifest` that module imported). It weakens **nothing**. Its stand-in
|
|
94
|
+
signature is a digest of the exact payload bytes, accepted only for those
|
|
95
|
+
bytes and only from the one issuer it signs as, so with the seam active an
|
|
96
|
+
unsigned token is still denied for `skcode.dispatch`, a tampered payload is
|
|
97
|
+
still denied, a signature lifted from another token is still denied, and a
|
|
98
|
+
well-formed signature declaring a different issuer is still denied. The
|
|
99
|
+
verified-tier enrollment floor, `signature_verifies`, and the
|
|
100
|
+
raise-on-signing-failure behaviour are all untouched.
|
|
101
|
+
- It cannot be switched on by accident in a deployed process: nothing in
|
|
102
|
+
CapAuth's runtime imports it, importing it requires `pytest` (a dev extra, not
|
|
103
|
+
a runtime or `service` dependency), it registers no `pytest11` entry point so
|
|
104
|
+
pytest never auto-loads it, importing it patches nothing, every activation is
|
|
105
|
+
lexically scoped and reverts, and there is deliberately no env var or global
|
|
106
|
+
flag that enables it.
|
|
107
|
+
- `tests/conftest.py` now imports the shipped fixture rather than carrying a
|
|
108
|
+
second copy. New suite `tests/test_testing_helper.py` pins every claim above,
|
|
109
|
+
including one control that runs in a fresh subprocess (empty `sys.modules`, no
|
|
110
|
+
pytest session) and asserts its own isolation before asserting that
|
|
111
|
+
`issue_token` still raises on a genuine signing failure.
|
|
112
|
+
|
|
9
113
|
### Fixed
|
|
10
114
|
|
|
115
|
+
- **An unreadable coordination projection rendered identically to an agent with
|
|
116
|
+
no collaborators** (coord card `49e9b427`).
|
|
117
|
+
`capauth.trust.graph._add_coord_agents` reads
|
|
118
|
+
`~/.skcapstone/coordination/agents/*.json`, a rebuildable PROJECTION of the
|
|
119
|
+
coordination board, and turns `len(completed_tasks)` into a trust edge weight
|
|
120
|
+
via `0.3 + 0.05n`. It continued past `json.JSONDecodeError` and `OSError` per
|
|
121
|
+
file, and skdashboard collapses any exception out of `build_trust_graph` into
|
|
122
|
+
an empty graph, so a projection that could not be read at all produced zero
|
|
123
|
+
coord edges, no warning, and the same picture in every output format as a
|
|
124
|
+
healthy agent that simply has no collaborators. Per-file tolerance is kept (one
|
|
125
|
+
corrupt file must not blank the trust web), but the failures are now counted
|
|
126
|
+
and reported:
|
|
127
|
+
- `capauth.trust.SourceHealth`, a new exported record on `TrustGraph`, with
|
|
128
|
+
statuses `ok` / `absent` / `degraded` / `unreadable`, file counts, the source
|
|
129
|
+
path, and a bounded sample of the read errors. `TrustGraph.warnings()` is
|
|
130
|
+
empty only when every instrumented source was read in full, so an absent edge
|
|
131
|
+
can once again be read as an absent relationship.
|
|
132
|
+
- `format_json` gains `sources`, `warnings` and `complete` (this is the dict
|
|
133
|
+
skdashboard's trust panel already parses), `format_table` gains a Sources
|
|
134
|
+
section plus an explicit incomplete-graph warning, and `format_dot` draws a
|
|
135
|
+
red node for a degraded source so a rendered image shows the gap instead of
|
|
136
|
+
just being smaller.
|
|
137
|
+
- A JSON payload that parses but is not an object (an error page, an array, a
|
|
138
|
+
bare `null`) now counts as a failed file. Previously it raised an uncaught
|
|
139
|
+
`AttributeError` out of `build_trust_graph`, which the caller turned into an
|
|
140
|
+
empty graph.
|
|
141
|
+
- The directory listing uses `iterdir` rather than `glob`, because `glob`
|
|
142
|
+
swallows a `PermissionError` on the directory and returns nothing, reporting
|
|
143
|
+
an unreadable store as an empty one.
|
|
144
|
+
- Coord edges now carry `source`, `corroborated: False` and `tasks_claimed`
|
|
145
|
+
metadata, and the read carries a `# PROJECTION-OK:` marker whose
|
|
146
|
+
justification is conditional on a verified property: `TrustEdge.strength` is
|
|
147
|
+
consumed only by renderers (DOT `penwidth`, the ASCII bar, skdashboard's
|
|
148
|
+
stroke width), never by an authorization path.
|
|
149
|
+
|
|
150
|
+
Trust SEMANTICS are unchanged on purpose. The audit (188 of 1,683 claimed
|
|
151
|
+
completions backed by no store, identity fields describing the rebuilder,
|
|
152
|
+
Syncthing conflict resolution losing completions in one direction, and no
|
|
153
|
+
representation of the board's `review` column) and the recommendation to stop
|
|
154
|
+
deriving a weight from a completion count are in
|
|
155
|
+
`docs/TRUST_GRAPH_COORD_PROJECTION.md`, for the repo owner to decide. The
|
|
156
|
+
`0.3 + 0.05n` curve is now named (`COORD_BASE_STRENGTH`,
|
|
157
|
+
`COORD_STRENGTH_PER_TASK`, `COORD_SATURATION_TASKS = 14`) and locked by a test
|
|
158
|
+
so any future change to it is deliberate.
|
|
159
|
+
|
|
160
|
+
- **`verified` enrollment was unprovable for WebCrypto device keys**
|
|
161
|
+
(`inc-c72a9120`). N10's proof check was correct but PGP-only: it verified
|
|
162
|
+
`proof` solely through `capauth.crypto`'s armored-signature backend, while
|
|
163
|
+
skchat's operator devices present a base64 DER SPKI **WebCrypto ECDSA
|
|
164
|
+
P-256** key and sign with P1363 `r||s`. Those callers could not produce an
|
|
165
|
+
acceptable proof at all, so `enroll_device(mode="verified")` raised for
|
|
166
|
+
every real device link, and skchat's best-effort call site swallowed it:
|
|
167
|
+
a newly linked device enrolled successfully and silently received no
|
|
168
|
+
capabilities (no prekey, no inbox, and none of the VERIFIED-tier
|
|
169
|
+
`skchat.send` / `skchat.groups` / `skchat.calls` its own traffic needs).
|
|
170
|
+
`_proof_verifies` now dispatches on the key material itself, the same
|
|
171
|
+
`"BEGIN PGP"` discriminator `fingerprint_for()` already uses, and verifies
|
|
172
|
+
a device key through `pairing.operator_session.verify_device_signature`,
|
|
173
|
+
the ECDSA verifier this package already shipped, reused rather than
|
|
174
|
+
reimplemented. **N10 is not weakened**: `verified` still requires a real
|
|
175
|
+
signature over exactly `verified_challenge(fingerprint, subject)`, the
|
|
176
|
+
accepted key TYPE widened, not the requirement. Both branches stay
|
|
177
|
+
fail-closed, and a PGP key is never verified by the ECDSA path or the
|
|
178
|
+
reverse.
|
|
179
|
+
|
|
180
|
+
- **`tests/test_identity_class.py` did not enroll under the card N10 proof rules**,
|
|
181
|
+
so ten of its cases failed on `main` from the moment `09a6d6f3` merged. Its
|
|
182
|
+
`_enroll` helper still passed a placeholder armored pubkey with no `proof` /
|
|
183
|
+
`attestation`, which `enroll_device` now correctly refuses. It builds real
|
|
184
|
+
keypairs and real challenge signatures via the existing conftest helpers
|
|
185
|
+
instead. No production code involved; the identity-class ceiling itself was
|
|
186
|
+
never broken.
|
|
11
187
|
- **`enroll_device` accepted `verified` / `attested` as a caller-asserted claim,
|
|
12
188
|
never checked** (card N10 `09a6d6f3`). `Enrollment.proof` and
|
|
13
189
|
`Enrollment.attestation` were stored on the record but neither was ever
|
|
@@ -383,6 +383,18 @@ verify `POST /capauth/v1/verify` (`:266`), authz decision
|
|
|
383
383
|
(`:653`). **No `/health` route exists**; `/capauth/v1/status` is the liveness probe
|
|
384
384
|
(that is what the compose healthcheck curls).
|
|
385
385
|
|
|
386
|
+
**Trust graph.** `capauth.trust.graph.build_trust_graph` is a VISUALISATION, not
|
|
387
|
+
a decision input: `TrustEdge.strength` is read only by the renderers (DOT
|
|
388
|
+
`penwidth`, the ASCII bar, skdashboard's stroke width), and no authorization path
|
|
389
|
+
(`authz.py`, `tokens.py`, `service/`) imports the trust graph at all. One of its
|
|
390
|
+
inputs, the coordination `agents/*.json` projection, is known to be corrupt in
|
|
391
|
+
four measured ways and is read anyway under a `# PROJECTION-OK:` marker whose
|
|
392
|
+
justification depends on that separation holding. Every graph now reports per
|
|
393
|
+
source read health (`ok` / `absent` / `degraded` / `unreadable`) so an unreadable
|
|
394
|
+
input is distinguishable from a genuinely empty one. Audit, the marker's
|
|
395
|
+
conditions, and the proposed weighting change:
|
|
396
|
+
[docs/TRUST_GRAPH_COORD_PROJECTION.md](docs/TRUST_GRAPH_COORD_PROJECTION.md).
|
|
397
|
+
|
|
386
398
|
Full protocol + claim/token format: [docs/PROTOCOL.md](docs/PROTOCOL.md),
|
|
387
399
|
[docs/CLAIMS.md](docs/CLAIMS.md). Crypto detail: [docs/CRYPTO_SPEC.md](docs/CRYPTO_SPEC.md).
|
|
388
400
|
|
|
@@ -443,8 +455,18 @@ Full protocol + claim/token format: [docs/PROTOCOL.md](docs/PROTOCOL.md),
|
|
|
443
455
|
**SK = staycuriousANDkeepsmilin 🐧** — *capauth: you are not a user, you are a sovereign.*
|
|
444
456
|
|
|
445
457
|
<!-- docs-evidence
|
|
446
|
-
verified: 2026-08-
|
|
458
|
+
verified: 2026-08-16
|
|
447
459
|
checks:
|
|
460
|
+
- name: the trust graph is still display-only, which is what section 7 and the PROJECTION-OK marker rest on
|
|
461
|
+
run: ! grep -rqE 'trust\.graph|build_trust_graph|TrustEdge' src/capauth/authz.py src/capauth/tokens.py src/capauth/service/
|
|
462
|
+
- name: the coord projection read still carries its PROJECTION-OK justification
|
|
463
|
+
run: grep -qF '# PROJECTION-OK:' src/capauth/trust/graph.py
|
|
464
|
+
- name: coord edges still declare their count uncorroborated
|
|
465
|
+
run: grep -qF '"corroborated": False' src/capauth/trust/graph.py
|
|
466
|
+
- name: the coord weight constants and their saturation point are still recorded, not inline magic
|
|
467
|
+
run: grep -qxF 'COORD_SATURATION_TASKS = 14' src/capauth/trust/graph.py && grep -qxF 'COORD_BASE_STRENGTH = 0.3' src/capauth/trust/graph.py
|
|
468
|
+
- name: a degraded source is still reported rather than swallowed into an empty graph
|
|
469
|
+
run: grep -qF 'def warnings(' src/capauth/trust/graph.py && grep -qF '"unreadable"' src/capauth/trust/graph.py
|
|
448
470
|
- name: both console-script entry points are exactly as section 7 documents
|
|
449
471
|
run: grep -qxF 'capauth = "capauth.cli:main"' pyproject.toml && grep -qxF 'capauth-service = "capauth.service.server:main"' pyproject.toml
|
|
450
472
|
- name: capauth-service still defaults to 127.0.0.1 and port 8420 (section 5 Scenario A)
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
# The coordination projection as a trust input
|
|
2
|
+
|
|
3
|
+
**Status:** audit landed, failure visibility fixed, weighting change PROPOSED and
|
|
4
|
+
NOT applied.
|
|
5
|
+
**Audited:** 2026-08-16, against the live fleet coordination store.
|
|
6
|
+
**Owner decision required:** yes. See "Recommendation to the repo owner" below.
|
|
7
|
+
**Refs:** coord card `49e9b427`, upstream `ebc927c3` (A5.1), `7dd497bc`.
|
|
8
|
+
|
|
9
|
+
## 1. What this document is for
|
|
10
|
+
|
|
11
|
+
`capauth.trust.graph` builds the trust web that `skcapstone trust graph`, the
|
|
12
|
+
skcapstone shell, the `trust_graph` MCP tool and skdashboard's trust panel all
|
|
13
|
+
render. One of its five input sources is the coordination board, and it reads
|
|
14
|
+
that board through a REBUILDABLE PROJECTION rather than through the board's
|
|
15
|
+
store of record.
|
|
16
|
+
|
|
17
|
+
That read was never audited, because the read lives in capauth and the store
|
|
18
|
+
lives in skcapstone. This document records the audit, states plainly what was
|
|
19
|
+
fixed, and puts the part that changes trust semantics in front of the repo
|
|
20
|
+
owner instead of quietly changing it.
|
|
21
|
+
|
|
22
|
+
## 2. The read, as it stands
|
|
23
|
+
|
|
24
|
+
`src/capauth/trust/graph.py::_add_coord_agents` opens every
|
|
25
|
+
`~/.skcapstone/coordination/agents/*.json`, takes `data["agent"]` as the
|
|
26
|
+
collaborator name, and computes:
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
completed = len(data.get("completed_tasks", []))
|
|
30
|
+
strength = min(1.0, 0.3 + completed * 0.05)
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
So a claimed completion count is a direct multiplier on a trust edge weight.
|
|
34
|
+
Fourteen claimed completions saturate the edge at 1.0, and every collaborator
|
|
35
|
+
at or above fourteen is thereafter indistinguishable from every other.
|
|
36
|
+
|
|
37
|
+
## 3. The audit: four independent ways the count is wrong
|
|
38
|
+
|
|
39
|
+
All four were measured on the live store on 2026-08-16, not estimated.
|
|
40
|
+
|
|
41
|
+
1. **1,683 completions are claimed across the agent projections, and 188 of
|
|
42
|
+
them are backed by no store at all.** Not the coordination event log, not
|
|
43
|
+
the archive manifest. Each uncorroborated claim inflates a trust edge by
|
|
44
|
+
0.05.
|
|
45
|
+
2. **The identity fields describe the rebuilder, not the agent.** 106 of about
|
|
46
|
+
120 agent files share a two-second-wide `last_seen`, and 100 carry the
|
|
47
|
+
rebuilder's hostname, because `export_to_legacy -> save_agent` restamps
|
|
48
|
+
`last_seen` and `AgentFile.host` defaults to `socket.gethostname()`. Any
|
|
49
|
+
freshness or provenance signal on these nodes is an artifact of whoever last
|
|
50
|
+
ran a rebuild.
|
|
51
|
+
3. **Conflict resolution loses completions in one consistent direction.** All 6
|
|
52
|
+
Syncthing conflict copies claimed completions that the surviving live file
|
|
53
|
+
had lost (autopilot 4 versus 0; opus-swarm 5, 11, 22, 5, 11 versus 0), and
|
|
54
|
+
never the reverse. The live counts are therefore systematically low, and
|
|
55
|
+
nothing about the surviving file reveals that.
|
|
56
|
+
4. **The projection cannot represent the board's `review` column at all.** 32
|
|
57
|
+
cards currently disagree between store and projection for structural
|
|
58
|
+
reasons that no rebuild can fix. A projection that cannot express a state of
|
|
59
|
+
the thing it projects is not a faithful view of it.
|
|
60
|
+
|
|
61
|
+
None of this is evidence of an attack. It is evidence that a trust weight is
|
|
62
|
+
computed from a number that is wrong in at least four unrelated ways, and that
|
|
63
|
+
nobody noticed because the reader and the writer live in different repos.
|
|
64
|
+
|
|
65
|
+
## 4. The aggravating factor, and the thing that was actually fixed
|
|
66
|
+
|
|
67
|
+
The whole consumer chain was built to hide this. `_add_coord_agents` continued
|
|
68
|
+
past `json.JSONDecodeError` and `OSError` per file, and
|
|
69
|
+
`skdashboard/src/skdashboard/dashboard.py` collapses every exception out of
|
|
70
|
+
`build_trust_graph` into an empty graph so the panel never 500s. The net
|
|
71
|
+
effect: a totally unreadable projection rendered as a graph with zero coord
|
|
72
|
+
edges, which is exactly what a healthy agent with no collaborators renders as.
|
|
73
|
+
Healthy and broken were the same picture, in all three output formats.
|
|
74
|
+
|
|
75
|
+
That is fixed. `TrustGraph` now carries a `SourceHealth` record per
|
|
76
|
+
instrumented source with four states:
|
|
77
|
+
|
|
78
|
+
| status | meaning |
|
|
79
|
+
| ------------ | -------------------------------------------------------------- |
|
|
80
|
+
| `ok` | read succeeded. Zero edges means zero relationships. |
|
|
81
|
+
| `absent` | the source does not exist on this home. |
|
|
82
|
+
| `degraded` | some records read, some failed. The graph UNDERCOUNTS. |
|
|
83
|
+
| `unreadable` | nothing could be read. Absence of edges means nothing at all. |
|
|
84
|
+
|
|
85
|
+
`TrustGraph.warnings()` is empty only when every instrumented source was read
|
|
86
|
+
in full. The three renderers surface it: `format_json` gains `sources`,
|
|
87
|
+
`warnings` and `complete` (this is the dict skdashboard renders), `format_table`
|
|
88
|
+
gains a Sources section and an explicit incomplete-graph warning, and
|
|
89
|
+
`format_dot` draws a red node for a degraded source so the gap is visible in a
|
|
90
|
+
rendered image rather than expressed as a smaller graph.
|
|
91
|
+
|
|
92
|
+
Two smaller correctness fixes came with it, both cases where "unreadable" was
|
|
93
|
+
being silently converted into "empty":
|
|
94
|
+
|
|
95
|
+
- A JSON payload that parses but is not an object (an error page, an array, a
|
|
96
|
+
bare `null`) is now counted as a failed file. Previously `[1,2,3]` raised an
|
|
97
|
+
uncaught `AttributeError` out of `build_trust_graph`, which skdashboard then
|
|
98
|
+
turned into an empty graph.
|
|
99
|
+
- The directory listing uses `iterdir` rather than `glob`, because `glob`
|
|
100
|
+
swallows a `PermissionError` on the directory and returns nothing, reporting
|
|
101
|
+
an unreadable store as an empty one.
|
|
102
|
+
|
|
103
|
+
Per-file tolerance is deliberately KEPT. One corrupt agent file should not
|
|
104
|
+
blank the whole trust web. The change is that tolerated failures are now
|
|
105
|
+
counted and reported instead of being invisible.
|
|
106
|
+
|
|
107
|
+
## 5. Why the projection is still read at all (the PROJECTION-OK marker)
|
|
108
|
+
|
|
109
|
+
The marker above `_add_coord_agents` justifies the read for a trust weight
|
|
110
|
+
specifically, and it rests on exactly one property, which was verified rather
|
|
111
|
+
than assumed:
|
|
112
|
+
|
|
113
|
+
> `TrustEdge.strength` is a DISPLAY quantity and nothing else.
|
|
114
|
+
|
|
115
|
+
Its only consumers fleet-wide are renderers: DOT `penwidth` in `format_dot`,
|
|
116
|
+
the ASCII bar in `format_table`, and skdashboard's `static/trust.html` stroke
|
|
117
|
+
width and label. No authorization, capability issuance, token gate or policy
|
|
118
|
+
decision in capauth or in any consumer reads it. Nothing under
|
|
119
|
+
`src/capauth/authz.py`, `src/capauth/tokens.py` or `src/capauth/service/`
|
|
120
|
+
imports the trust graph at all. An inflated coord edge draws a thicker line. It
|
|
121
|
+
does not grant anything.
|
|
122
|
+
|
|
123
|
+
That justification is CONDITIONAL, and the condition is the entire basis of the
|
|
124
|
+
marker. The moment any code path makes an access decision from a coord edge
|
|
125
|
+
weight, this read stops being acceptable and must move to the corroborated
|
|
126
|
+
source. Every coord edge therefore ships with `corroborated: False` in its
|
|
127
|
+
metadata so a future consumer cannot claim it was not told.
|
|
128
|
+
|
|
129
|
+
## 6. Recommendation to the repo owner
|
|
130
|
+
|
|
131
|
+
Two of the card's four questions change what trust MEANS, so they are proposed
|
|
132
|
+
here rather than applied. Both are yours.
|
|
133
|
+
|
|
134
|
+
### (a) Should trust weight derive from a completion count at all?
|
|
135
|
+
|
|
136
|
+
**Recommendation: no.** Reasoning, in order of weight:
|
|
137
|
+
|
|
138
|
+
1. **A count measures volume, not trustworthiness.** Fourteen trivial
|
|
139
|
+
completions produce the same maximal 1.0 edge as years of high-stakes work.
|
|
140
|
+
The formula rewards task churn, and the fleet's own agents are the ones
|
|
141
|
+
generating the churn.
|
|
142
|
+
2. **The channel is monotonic and one-directional.** Nothing decays and nothing
|
|
143
|
+
subtracts. An agent that failed two hundred cards and completed fourteen is
|
|
144
|
+
rendered as maximally trusted. A trust signal with no way to go down is not
|
|
145
|
+
measuring trust.
|
|
146
|
+
3. **It is precisely the quantity the store cannot corroborate.** 188 of 1,683
|
|
147
|
+
claims are backed by nothing (section 3.1). If a count is kept, it must read
|
|
148
|
+
the union of the coordination event log and the archive manifest, never
|
|
149
|
+
`agents/*.json`; per card `7dd497bc` an events-only read under-reports by 93
|
|
150
|
+
percent, so the union is not optional.
|
|
151
|
+
4. **The projection is structurally incomplete** (section 3.4) and its identity
|
|
152
|
+
fields are rebuild artifacts (section 3.2). It cannot be made faithful by
|
|
153
|
+
rebuilding it more often.
|
|
154
|
+
|
|
155
|
+
**Preferred replacement: make the coord edge a PRESENCE edge with a fixed
|
|
156
|
+
strength (0.3), and keep the count as a label and metadata only.** What the
|
|
157
|
+
projection can genuinely support is "these two agents appear on the same
|
|
158
|
+
coordination board". That is a real fact about collaboration, it survives all
|
|
159
|
+
four corruption modes above, and it is all the projection is entitled to
|
|
160
|
+
assert. The human reader still sees "collaborator (7 tasks)" in the label; the
|
|
161
|
+
number simply stops moving a weight. This is also the smallest possible change:
|
|
162
|
+
one constant, no new data source, and the saturation cliff disappears because
|
|
163
|
+
the ramp disappears.
|
|
164
|
+
|
|
165
|
+
**Second option, if a ramp is wanted:** source it from the corroborated union
|
|
166
|
+
(event log plus archive manifest), make it logarithmic rather than linear so
|
|
167
|
+
volume has diminishing returns, and add recency decay. Note that recency decay
|
|
168
|
+
needs a corroborated timestamp, because `last_seen` on these files is the
|
|
169
|
+
rebuilder's clock (section 3.2). This is real work and it still measures
|
|
170
|
+
volume, so I would only do it if the trust graph is going to become an input to
|
|
171
|
+
something that decides.
|
|
172
|
+
|
|
173
|
+
**Third option:** keep the status quo plus the marker, which is what this
|
|
174
|
+
change ships as an interim. Acceptable only while section 5 stays true.
|
|
175
|
+
|
|
176
|
+
### (d) Record or replace `0.3 + 0.05n`
|
|
177
|
+
|
|
178
|
+
Recorded, not silently kept. The constants are now named in `graph.py` as
|
|
179
|
+
`COORD_BASE_STRENGTH`, `COORD_STRENGTH_PER_TASK` and `COORD_SATURATION_TASKS`
|
|
180
|
+
(14), the saturation point is stated in the code rather than left to be
|
|
181
|
+
discovered, and `tests/test_trust_graph_coord_audit.py` locks the exact curve at
|
|
182
|
+
n = 0, 3, 14 and 200 so that any future change to it is a deliberate, reviewed
|
|
183
|
+
edit rather than a drift.
|
|
184
|
+
|
|
185
|
+
That test is a lock, not an endorsement. If (a) is accepted, the lock changes in
|
|
186
|
+
the same commit as the formula and the reasoning goes here.
|
|
187
|
+
|
|
188
|
+
## 7. Follow-ups this change does NOT do
|
|
189
|
+
|
|
190
|
+
- **skdashboard still collapses every exception into an empty graph** with a
|
|
191
|
+
`note` field. It now has `sources` and `complete` available in the payload it
|
|
192
|
+
already parses, and its trust panel should surface both, but that is another
|
|
193
|
+
repo and another card.
|
|
194
|
+
- **The other file-scanning readers in `graph.py`** (`_add_token_edges`,
|
|
195
|
+
`_add_feb_edges`, `_add_sync_edges`) still swallow per-file failures without
|
|
196
|
+
counting them. `SourceHealth` is deliberately general so they can be
|
|
197
|
+
instrumented the same way. Coord was done first because it is the one whose
|
|
198
|
+
source is known to be corrupt.
|
|
199
|
+
- **Nothing in this change touches the coordination store or its rebuilder.**
|
|
200
|
+
The four corruption modes in section 3 are still live. This change makes
|
|
201
|
+
capauth stop presenting them as fact; it does not fix them.
|
|
@@ -91,6 +91,7 @@ from .trust import (
|
|
|
91
91
|
CALIBRATION_FILENAME,
|
|
92
92
|
DEFAULT_THRESHOLDS,
|
|
93
93
|
FORMATTERS,
|
|
94
|
+
SourceHealth,
|
|
94
95
|
TrustEdge,
|
|
95
96
|
TrustGraph,
|
|
96
97
|
TrustNode,
|
|
@@ -191,6 +192,7 @@ __all__ = [
|
|
|
191
192
|
"has_scope",
|
|
192
193
|
# trust (kernel track M1: moved verbatim from skcapstone)
|
|
193
194
|
"FORMATTERS",
|
|
195
|
+
"SourceHealth",
|
|
194
196
|
"CALIBRATION_FILENAME",
|
|
195
197
|
"DEFAULT_THRESHOLDS",
|
|
196
198
|
"TrustEdge",
|
|
@@ -207,7 +209,22 @@ __all__ = [
|
|
|
207
209
|
"save_calibration",
|
|
208
210
|
]
|
|
209
211
|
|
|
210
|
-
|
|
212
|
+
# The git tag is the real version: pyproject declares `dynamic = ["version"]`
|
|
213
|
+
# and setuptools_scm derives it at build time. A hardcoded literal here does not
|
|
214
|
+
# feed packaging, it only shadows it, so it drifts silently and reports a version
|
|
215
|
+
# that has not been true since 0.2.15. That is not a cosmetic problem: on
|
|
216
|
+
# 2026-08-16 a fleet audit read this attribute and concluded three nodes ran
|
|
217
|
+
# 0.2.15 when every one of them had 0.3.0 installed, which inverted the
|
|
218
|
+
# risk assessment for a node that could not sign. Read the installed
|
|
219
|
+
# distribution metadata instead, so this attribute can only ever agree with what
|
|
220
|
+
# pip actually resolved.
|
|
221
|
+
try: # pragma: no cover - trivial, and the fallback is exercised below
|
|
222
|
+
from importlib.metadata import PackageNotFoundError
|
|
223
|
+
from importlib.metadata import version as _dist_version
|
|
224
|
+
|
|
225
|
+
__version__ = _dist_version("capauth")
|
|
226
|
+
except PackageNotFoundError: # running from a source tree with no install
|
|
227
|
+
__version__ = "0.0.0.dev0"
|
|
211
228
|
|
|
212
229
|
SKCAPSTONE_HOME = Path.home() / ".skcapstone"
|
|
213
230
|
DEFAULT_CAPAUTH_DIR = SKCAPSTONE_HOME / "capauth"
|
|
@@ -66,6 +66,13 @@ from .operator_session import (
|
|
|
66
66
|
verify_device_signature,
|
|
67
67
|
verify_operator_session,
|
|
68
68
|
)
|
|
69
|
+
from .proof import (
|
|
70
|
+
EnrollmentProof,
|
|
71
|
+
ProofSigningError,
|
|
72
|
+
build_attested_proof,
|
|
73
|
+
build_verified_proof,
|
|
74
|
+
enrollment_challenge,
|
|
75
|
+
)
|
|
69
76
|
from .records import (
|
|
70
77
|
MODE_SEVERITY,
|
|
71
78
|
DeviceRecord,
|
|
@@ -77,6 +84,7 @@ from .records import (
|
|
|
77
84
|
from .store import (
|
|
78
85
|
SIDECAR_KEY,
|
|
79
86
|
SIDECAR_VERSION,
|
|
87
|
+
SKCAPSTONE_HOME_ENV,
|
|
80
88
|
PairingStore,
|
|
81
89
|
default_base_dir,
|
|
82
90
|
fingerprint_for,
|
|
@@ -93,6 +101,12 @@ __all__ = [
|
|
|
93
101
|
"PairingError",
|
|
94
102
|
"verified_challenge",
|
|
95
103
|
"attested_challenge",
|
|
104
|
+
# supported proof construction (the constructor for the N10 requirement)
|
|
105
|
+
"build_verified_proof",
|
|
106
|
+
"build_attested_proof",
|
|
107
|
+
"enrollment_challenge",
|
|
108
|
+
"EnrollmentProof",
|
|
109
|
+
"ProofSigningError",
|
|
96
110
|
# records + modes
|
|
97
111
|
"EnrollmentMode",
|
|
98
112
|
"Enrollment",
|
|
@@ -106,6 +120,7 @@ __all__ = [
|
|
|
106
120
|
"PairingStore",
|
|
107
121
|
"SIDECAR_KEY",
|
|
108
122
|
"SIDECAR_VERSION",
|
|
123
|
+
"SKCAPSTONE_HOME_ENV",
|
|
109
124
|
"default_base_dir",
|
|
110
125
|
"fingerprint_for",
|
|
111
126
|
# canonical-subject rewrite (card N5)
|
|
@@ -72,20 +72,53 @@ def _proof_verifies(
|
|
|
72
72
|
) -> bool:
|
|
73
73
|
"""Fail-closed signature check: True only for a real, matching signature.
|
|
74
74
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
75
|
+
Accepts either key shape a caller may present, dispatched on the key
|
|
76
|
+
material itself -- the same ``"BEGIN PGP" in key`` discriminator
|
|
77
|
+
:func:`capauth.pairing.store.fingerprint_for` already uses to tell an
|
|
78
|
+
armored PGP key from an opaque one, never a caller-supplied "which
|
|
79
|
+
algorithm" flag (a caller that picks its own verifier could pick a weak
|
|
80
|
+
one):
|
|
81
|
+
|
|
82
|
+
* An ASCII-armored PGP key: ``signature_armor`` must be a detached
|
|
83
|
+
signature over ``challenge``, verified by capauth's PGP backend
|
|
84
|
+
(unchanged from before this widening).
|
|
85
|
+
* Anything else is treated as a base64 DER SPKI WebCrypto ECDSA P-256
|
|
86
|
+
device key (skchat/skcode's device-linking key shape):
|
|
87
|
+
``signature_armor`` must be a base64 ECDSA signature -- WebCrypto's
|
|
88
|
+
64-byte P1363 ``r||s`` or DER -- over ``challenge``, verified via
|
|
89
|
+
:func:`capauth.pairing.operator_session.verify_device_signature` (the
|
|
90
|
+
one ECDSA verifier this package already ships, reused here rather than
|
|
91
|
+
re-implemented).
|
|
92
|
+
|
|
93
|
+
Any failure mode -- missing key, missing signature, unparseable armor or
|
|
94
|
+
base64/DER, a signature over different bytes, a signature from a
|
|
95
|
+
different key, a crypto backend that is unavailable in this interpreter
|
|
96
|
+
-- returns False rather than raising, so a caller who cannot produce a
|
|
97
|
+
real proof is refused with the same "invalid proof" reason as a caller
|
|
98
|
+
who forged one, never with an unrelated crash. A PGP-armored key is never
|
|
99
|
+
handed to the ECDSA path or vice versa: the branch is chosen once, up
|
|
100
|
+
front, from the key material alone.
|
|
81
101
|
"""
|
|
82
102
|
if not pubkey_armor or not signature_armor:
|
|
83
103
|
return False
|
|
104
|
+
if "BEGIN PGP" in pubkey_armor:
|
|
105
|
+
try:
|
|
106
|
+
from capauth.crypto import get_backend
|
|
107
|
+
|
|
108
|
+
return bool(get_backend().verify(challenge, signature_armor, pubkey_armor))
|
|
109
|
+
except Exception: # noqa: BLE001 -- any backend/parse failure means "not proven"
|
|
110
|
+
return False
|
|
84
111
|
try:
|
|
85
|
-
from
|
|
112
|
+
from .operator_session import verify_device_signature
|
|
86
113
|
|
|
87
|
-
return bool(
|
|
88
|
-
|
|
114
|
+
return bool(
|
|
115
|
+
verify_device_signature(
|
|
116
|
+
device_pubkey_b64=pubkey_armor,
|
|
117
|
+
payload=challenge,
|
|
118
|
+
sig_b64=signature_armor,
|
|
119
|
+
)
|
|
120
|
+
)
|
|
121
|
+
except Exception: # noqa: BLE001 -- malformed b64/DER/key means "not proven"
|
|
89
122
|
return False
|
|
90
123
|
|
|
91
124
|
|