Release: merge development into beta - #834
github-actions[bot] wants to merge 333 commits into
Conversation
| runs-on: ubuntu-latest | ||
| # A build and two browser starts; minutes at most. | ||
| timeout-minutes: 20 | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| - uses: actions/setup-node@v4 | ||
| with: | ||
| node-version: "24" | ||
| cache: npm | ||
| - run: npm ci --ignore-scripts | ||
| - run: npm run build:extension | ||
| - name: Chromium loads the package | ||
| run: | | ||
| npx playwright install --with-deps chromium | ||
| node browser-extension/load-check/chromium.mjs | ||
| - name: Firefox loads the package | ||
| run: | | ||
| npm install --no-save selenium-webdriver@4 | ||
| firefox --version | ||
| geckodriver --version | ||
| node browser-extension/load-check/firefox.mjs | ||
| - uses: actions/upload-artifact@v4 | ||
| with: | ||
| name: browser-extension | ||
| path: browser-extension/dist/ | ||
| retention-days: 14 |
…the CLI sdk.yml tests the Go, Python and TypeScript libraries on every pull request that touches sdk/, builds the Python distribution and the npm tarball as a dry run, and publishes on sdk-py-v* (PyPI trusted publishing) and sdk-js-v* (npm with provenance); sdk/go/v* tags are the Go release. cli-release.yml now also runs on sdk/go changes, attaches SHA256SUMS to each cli-v* release and pushes ghcr.io/conductionnl/keepiq-cli built from the release binaries. integrations.yml runs the action and the GitLab template against the stub Keepiq. Refs #776
…ries-and-ci Adds docs/client-libraries.md, docs/ci-integrations.md and sdk/README.md, ticks every task with what was verified here and what is owed, syncs the two new capabilities into openspec/specs and sets parity rows apps-18 and apps-21 to built. Closes #776
…refusal audit trail, #804 thread follow-ups (#891) * fix(suites): refuse a private-key re-wrap during a migration or on a suite that is not active updatePrivateKey now refuses with 409 when the suite is either end of an in-progress migration (assertNoMigrationInProgress, its third call site) or when its status is not active. Whoever holds a leaked old password could otherwise re-wrap the old suite's envelope mid-recovery and strand every record not yet migrated, or re-wrap a revoked suite before a reinstate. Fixes #869 * fix(migration): proof-gate the abort and audit abort, termination and refusals Abort now needs a vault-key proof over the NEW suite's key, bound to the migration id, so a stolen session can no longer call the owner's compromise recovery off every time it starts. The resume banner asks for the current master password to abort. Audit plumbing (keepiq#870): suite.recovery_aborted (dispatched by abortMigration, with the acting user), suite.migration_terminated (dispatched by terminateForCompromise), suite.revoke_refused (EncryptionSuiteService::recordRevokeRefused, for the force-revoke refusals) and key_proof.refused (VaultKeyProofMiddleware::afterException). Recovery start is now audited at the start; markCompromised recorded recovery_started at completion and recovery_completed never. The resume banner's toast counts only contacts the rotation removed for the re-add sentence and warns separately, without a re-add nudge, about a contact whose break-glass was in flight (keepiq#880 item 1). Fixes #859 * fix(proofs): make every vault-key proof single-use through the database A used proof's nonce hash is now inserted into keepiq_used_proofs, unique on the hash, instead of a distributed cache. The cache made single use silent best-effort: a NullCache without a memcache, and per node with APCu alone. The insert holds on every install and across cluster nodes; any other insert failure refuses the proof (fail closed). Expired rows are swept on each use. New table in a new migration and in the consolidated schema; version bumped to 0.3.4-unstable.20261002120000. The DB-backed mapper test runs in CI against the installed app and skips on a bare checkout. Fixes #868 * docs(architecture): list all nine vault-key-proof routes and both exclusions Regenerated the 4.2 table from the attributes: revoke binds acceptEmergencyLoss, and abort, re-envelope, designate and GDPR delete were missing. The exclusion list now names forceRevoke with its reason, and the section says the locked-out owner's revoke path is the admin force-revoke. Single use is described as the database store, and abort as guarded. Fixes #879 * fix(emergency-access): resolve the open #804 review threads - The recovery form's removed-contacts note no longer says the rotation was resumed: an initiate run whose contacts could not be listed lands on the same screen. The vitest for the resumed case now uses the live resumed state (no result, no retained password), and a new one pins that an initiate run with an empty list never reads 'was resumed'. - Spec delta: scope the 'told about every removed contact' sentence to the owner's own completions and point the admin force-revoke case at #876; scope the banner count to completions the banner performs; state that a break-glass requested after the grantor confirmed counts as in flight. - EmergencyEnvelopeInvalidationService class docblock: the sweep catches every contact the loop did not carry, not only unreachable grantees. - Translations for every new string in all catalogues; the replaced 'was resumed' string is removed. Refs #880 * docs(openspec): archive fix-app-suite-ownership-guard Every task is done: PR #676 merged on 12 Sep with the guard and its two refusal tests; the three merge-time tasks are closed with the evidence in tasks.md. The ownership-boundary requirement is synced into openspec/specs/encryption-suites. Closes #675 Closes #680 * test(emergency-access): browser-built envelope fixture and two-rotation regression; archive the change - Task 4.4: tests/fixtures/emergency-envelope.json is built by the browser's own buildRecoveryEnvelope (generator script alongside) and passes the server shape check in a PHPUnit test. - Task 4.6: a contact is carried A to B to C through two rotations, and a late re-point from the first rotation is refused. - Task 5.5: PR #678 already disclosed AI use and linked #673. Every task is now done, so migrate-emergency-access-on-rotation is archived and its deltas synced into openspec/specs (emergency-access, encryption-suites). The old 'Suite rotation invalidates envelopes' scenario keeps its name and now describes the residual sweep. Closes #674 Closes #700 * docs(openspec): harden-vault-key-material-guards status after the lane A2 fixes 7.5 is done (PR #677). 6.5, 6.6 and 7.6 need a running instance and stay open, so the change is not archived yet. * fix(audit): a refused force-revoke and a compromise migration end reach the audit trail recordRevokeRefused() had no caller, so gate-57 flagged it and no refusal was ever recorded. Every refusal arm of forceRevoke() now records it with a fixed reason code, and the compromise migration end names the admin. Refusal tests drive it through the controller (red before, green after). Prettier on five files this branch added. Refs #870
sdk/go/keepiqtest is the in-memory Keepiq machine API the Go library tests against, exported so the Kubernetes operator, the rotation runner and the Terraform provider test against the same stub. The library gains GetByNameIfNoneMatch (an ETag the caller stores, for example in a resource status), LeaseSupported and RenewLease. Refs #776
…salm OnwardShareGuard becomes Secret::assertOnwardShareable() and isRestrictedCopy(); ShareRestriction's parsing and combining move to ShareRestrictionRules; the holder edit refusal moves to the entity; DirectShareRegistrar splits its row checks. Behaviour is unchanged and every refusal test still passes. Refs #791
… Kubernetes Secrets integrations/kubernetes is a controller-runtime operator on the Go library in sdk/go. KeepiqConnection names the instance, the application and the Kubernetes Secret with its private key (and, optionally, its certificate). KeepiqSecret maps key, login or additionalFields.<name> of named Keepiq secrets to keys of a target Secret it owns. Each loop reads by name with the ETag kept in status, decrypts in the operator process, writes the target only when the data changed, and patches a checksum annotation on the restart targets on a rotation. Leases are recorded, renewed before they lapse, and a refused renewal reads again. 404, 409 (with candidate ids and folders), a refused token and a key mismatch set Ready=False with an event and leave the target alone; no value reaches status, events or logs. envtest tests (kube-apiserver 1.30) cover CRD validation, sync, recreate, rotation, every failure reason and leases, against the shared keepiqtest stub. Refs #777
…r-application lease policy (lane L) (#913) * feat(ca): renew the root from the CA health section, behind a confirmation The renew-root endpoint had no caller, so an admin warned about the root's expiry could not act on it. The CA health section now has a renew root button that opens a confirmation dialog; only the confirm posts, and the result names how many suites were signed again. A controller test pins the admin-only posture of every CA write. Fixes #741 * fix(extension): apply the org password policy before saving a login The extension saved and updated logins without the strength floor or the breach block that the web app applies. The rules now live in src/policy/rules.js and src/health/hibpMatch.js, free of Nextcloud page imports, and the extension bundles the same code. The worker reads the same policy endpoint, refuses before encrypting, and the in-page bar explains the refusal instead of offering a save. Refs #746 * feat(leases): view and set an application's lease policy on its detail page The per-application lease-policy override had a PUT and no screen. A new GET /api/v1/applications/{id}/lease-policy answers the policy in force, the stored override and the instance values, for an admin or the application's registrant (404 for anyone else). The leases panel now shows a form: an admin saves the override, an empty field inherits the instance value, and the registrant sees it read-only. Refs #753 * fix(extension): post the breach-check prefix in the body, as the route now expects Development moved the range route to POST with the prefix in the body (keepiq#866). The extension's new lookup follows it, with a test that the prefix never appears in the URL. Refs #746 * style: prettier on files this branch added
…docs The chart installs the operator with a namespaced Role by default and a ClusterRole on clusterWide=true; test/chart.sh runs helm lint and template snapshots. The chart README and docs/kubernetes.md document syncing and the no-Secret recipe: an init container runs the new keepiq install <path> (the CLI image has no shell) and the app starts through keepiq ci run. test/kind.sh runs the operator and the recipe on kind against the stub Keepiq (stub_server.py gains --host). integrations-kubernetes.yml runs envtest, the chart tests and kind on pull requests, and on k8s-v* pushes a cosign-signed multi-arch image and the OCI chart. A live sync test runs when KEEPIQ_LIVE_URL is set. Refs #777
Ticks every task with what was verified and what is owed, syncs the kubernetes-integration capability into openspec/specs and sets parity row apps-17 to built. Closes #777
…nrolls again (#923) * fix(passkey): do not replace a working passkey when the same device enrolls again Every enrolment uses the same user handle, so an authenticator that already holds a Keepiq passkey replaced it. The earlier row stayed listed as active but could never unlock again. Enrolment now sends the active passkeys as excludeCredentials and turns the authenticator's refusal into a clear message. Stale passkeys are not excluded, so re-enrolling after a master password change still works on the same device. (cherry picked from commit 5dce744) * fix(passkey): keep the authenticator's refusal as the error cause
… stopped the agent
PUT /api/v1/app/secrets/{id} honours If-Match: a stale strong ETag
answers 412 with the current ETag and writes nothing; no header writes
as before; another vault stays a 404. The envelope's secret block gains
expiresAt, and discovery advertises conditionalWrite and expiresAt.
The CLI fixture gains expiresAt: null; Newman asserts both additions.
(Completes WIP 857aed1.)
Refs #778
The Go library writes back with If-Match (ErrPreconditionFailed on 412), reads secret.expiresAt, and exposes the public key and Decrypt for values a caller keeps itself. The keepiqtest stub honours If-Match. Refs #778
…#896) * feat(team-folders): server side of automatic member confirmation An admin policy switch team_folder_auto_confirm (off by default) joins the org policy keys, so it is validated, audited with before and after, and readable in GET /api/settings/policy. GET /api/v1/team-folders/pending-confirmations tells the session user which folders they may confirm and which pairs wait: the owner sees every missing pair, a member only pairs where they hold write and a copy no older than the source's last key change, with that copy's id. POST /api/v1/team-folders/{id}/shares keeps the owner path, and accepts a non-owner row only with the switch on, write grade on the source, a covered enabled target with an active suite, a source inside the folder and a current own copy. The owner gets team_folder_member_confirmed and the audit records the confirmer as actor. The reconcile resolver also skips disabled accounts; this is the same change as in #895, so the two merge cleanly. Refs #770 * feat(team-folders): the browser confirms new members after unlock After any unlock (password, passkey or offline) the session starts the automatic confirmation: it asks for pending confirmations, decrypts the owner's source or the member's own copy once with the session key, encrypts per recipient and posts only ciphertext in chunks. It repeats every 15 minutes while the switch is on, stops on lock, never throws and shows one quiet notice per run. An admin section switches it on. The team folder dialog shows who confirmed which member and, with the switch on, that the folder waits for a member with write access. The reconcile response names the confirmers. New strings in all 37 catalogues. Closes #770 * fix(team-folders): check findings on automatic confirmation The confirmer's copy rules move into ConfirmerCopyResolver, so the confirmation service takes 8 collaborators instead of 10 (phpmd ExcessiveParameterList). No inline if in pendingForFolder(); the policy key comment starts with a capital. The user policy keeps team_folder_auto_confirm: the spec requires GET /api/settings/policy to expose it, every member's browser reads it, and it is a switch, not a group list; SettingsServicePolicyTest now names it. @nextcloud/dialogs is loaded on demand in the team folder store, so node-run specs that import it (attachment-crypto, bulk-store) no longer need a window. Refs #770 * style: prettier on files this branch added
…nations integrations/runner is a Go module on sdk/go. runner.yaml names Keepiq secrets only (unknown keys refused). Rotation is prove-then-record: generate, journal both values encrypted to the application key, set at the target, log in with the new value, then PUT with If-Match; a failed login or a 412 sets the old value back. Start-up recovery completes a journalled rotation whose new value logs in. Rotations run on a cron schedule or ahead of expiresAt. Sync polls updated_since and pushes by ETag with backoff to AWS Secrets Manager, Azure Key Vault, GitHub Actions secrets (sealed box) or an exec hook. Logs redact every value the runner handled. Tests follow. Refs #778
The crash test runs the rotation in a child process that exits right after the target accepted the new value; the next start completes the write-back from the journal. Other tests: prove-then-record, set-back on a failed login, 412 conflict restores the target and is not retried, a journal entry that never reached the target is dropped, generator classes, cron and expiry lead time. Each asserts no value in the log, the state files or any request to Keepiq. Refs #778
Sync pushes a changed secret once and an unchanged one never, and a failed push backs off without blocking the others. Destinations are tested against stubs of the Secrets Manager JSON protocol, Azure token and Set Secret, and the GitHub secrets API, where the sealed box opens only with the repository key. The postgres and mysql connectors were run against postgres:16 and mysql:8.4 (KEEPIQ_TEST_POSTGRES / KEEPIQ_TEST_MYSQL): the new password logs in, the old one does not. run --once rotates and syncs; the daemon waits for the schedule or the expiry lead time. Refs #778
integrations-runner.yml tests the runner with PostgreSQL and MySQL service containers on pull requests, and on runner-v* tags attaches static binaries with SHA256SUMS and pushes a cosign-signed multi-arch image ghcr.io/conductionnl/keepiq-runner. docs/rotation-and-sync.md covers setup, one application per runner, every connector and destination; runner.example.yaml passes keepiq-runner check. Refs #778
…890) * fix(suites): contain the whole account on a compromise force-revoke The compromise cascade ran as a listener on the revoke event. It failed open (one throw stopped it for every remaining secret and the admin got 200), it read ShareTargets the first revoke of a migration had already swept, it named one secret per owner, and it never touched link shares, passkeys, sessions, emergency grantors or share recipients. It now runs from CompromiseContainmentService, called by the force-revoke: - the blast radius of both migration ends is collected before any revoke, and the revoke sweep only deletes ShareTargets of copies on the revoked suite (#864); - every step contains and counts its own failure; the response carries cascade.failed and cascadeIncomplete (#863); - each owner gets one notice naming the first secret and counting the others (#875); - holders of copies of the revoked user's secrets and grantors of an approved emergency grant to them are warned (#872); - link shares and passkeys are revoked through the helper the owner's recovery uses (#858), and every session and app password is ended (#860); - the owner is told how many emergency contacts any force-revoke deleted, and the second suite's count is returned (#876, #877 backend). Fixes #858 Fixes #863 Fixes #864 Fixes #872 Fixes #875 * fix(suites): ask for the password before re-enrolling after a revocation After a force-revoke the user has no active suite, and create() accepted any session. A stolen session could enrol a key pair it owns and become the victim's identity for every new share and emergency designation. create() now refuses a user whose suites are all revoked or replaced with error reauthentication_required. The new POST /api/v1/suites/reenrol does the same enrolment behind #[PasswordConfirmationRequired]; the store confirms the password and retries there. Together with the session and app-password invalidation of the compromise cascade (previous commit), this closes the takeover. SSO accounts that cannot confirm a password pass the sudo guard (#871, a separate policy item); ending their sessions is what covers them. Fixes #860 * fix(suites): a compromise revoke cannot be undone, and the admin sees all of it Reinstate had no sudo, and nothing could refuse it for a suite the administrator had just revoked as compromised; it could also leave an owner with two active suites. - reinstate() carries #[PasswordConfirmationRequired]; the store runs the password confirmation first. - reinstateSuite() refuses with 409 when the last SUITE_REVOKED audit entry has markCompromised true, when no such entry exists (fail closed), and when the owner already has another active suite. - The admin result card hides Reinstate after a compromise revoke, shows the second suite and ended migration of a compromise revoke during a migration with that suite's emergency count, and shows when part of the compromise response failed. Spec: two requirements added to admin-suite-revocation (reinstate refusal, re-enrolment confirmation). Five new UI strings in every catalogue. Fixes #865 Fixes #877 * style(suites): named argument in ReinstateRefusedException * docs(openspec): archive admin-suite-revocation Every task is done, including section 6 for the #691 review follow-ups (#858 #860 #863 #864 #865 #872 #875 #876 #877). design.md records how D2 and D6 changed; #817 (delegations on a compromise revoke) stays open as a policy decision. Delta specs merged into openspec/specs, and @SPEC tags now point at openspec/specs instead of the archived change. Closes #702 Closes #725 * refactor(sharing): bind the suite-scoped sweep's subquery like SecretListOrganisation does * refactor(suites): move the reinstate guard into its own class; phpmd back to zero The guard that refuses a reinstate of a compromised suite (keepiq#865) now lives in SuiteReinstateGuard, which owns the audit-trail read. Without an injected trail it still refuses every reinstate (fail closed). EncryptionSuiteService keeps one new dependency and a reasoned coupling suppression; reenrol() gets a reasoned TooManyPublicMethods suppression, because PasswordConfirmationRequired is read per action. A shorter name for the emergency contact mapper in CompromiseContainmentService. * style: prettier on four files this branch added
Ticks every task with what was verified and what is owed (4.3 needs the dev instance), syncs secret-store-api and the new secret-rotation-runner spec, and sets parity rows apps-20 and apps-25 to built. Closes #778
…values integrations/terraform-provider-keepiq is a terraform-plugin-framework provider on sdk/go. ephemeral keepiq_secret reads and decrypts a value for one run; resource keepiq_secret takes value_wo, login_wo and additional_fields_wo as write-only arguments, writes only when value_wo_version changes (with If-Match against the refreshed ETag), adopts by import, and on destroy leaves the secret in Keepiq with a warning naming it. data keepiq_secret_metadata has no value attribute. Clients without write-only support are refused with a clear message. An end-to-end test drives Terraform 1.11.4 against the provider and the keepiqtest stub and fails when a saved plan or the state holds a value. The SDK exposes the envelope's certificate fingerprint; the stub keeps every issued token valid, as the server does. Refs #779
Both sides kept: Secret gains useOnly/accessExpiresAt next to pendingAdditionalFields (#888), the team-folder dialog keeps the use-only fields next to auto-confirm (#896), and every @SPEC clash keeps both tags. l10n is the union. SecretService::update chains the holder edit check to stay under phpmd's 100-line limit.
docs/ is generated with tfplugindocs from the provider's real schema (scripts/docs.sh; CI fails when it is not current). Examples for every resource. integrations-terraform.yml runs the unit and end-to-end tests with Terraform 1.11.4 and, on tf-v*, copies the module to the mirror repository with sdk/go pinned to its latest tag, where GoReleaser signs the release (.goreleaser.yml, registry manifest). docs/terraform.md is the user guide. Refs #779
Quality Report — ConductionNL/keepiq @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| test-l10n | ✅ | ||||
| format | ✅ | ||||
| check-l10n-js | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| composer | ✅ | ✅ 114/114 | |||
| npm | ✅ | ✅ 660/660 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ✅ | ||||
| lockfile sync | ✅ | ||||
| PHPUnit | ❌ | ||||
| Newman | ❌ | ||||
| Playwright | ❌ | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-10-04 15:20 UTC
Download the full PDF report from the workflow artifacts.
…h a capture script and workflow (#1149) * feat(extension): capture screenshots and videos of the browser extension A Playwright script loads the Chromium package and records pairing, unlocking, the vault, the generator, a send, filling a login, the save bar and a passkey, against one Nextcloud with Keepiq and OpenRegister (compose.yaml + setup.sh) and local demo sites (fixtures.mjs). * fix(extension): give the capture's demo sites their own domains This site lists every login of the same registrable domain, so the demo sites move to example.com, .net, .org and .example. The capture also ends old sends and shows the send list after a new link. * ci(extension): capture the docs media in a workflow, with Firefox screenshots docs-media.yml starts the capture instance, builds Keepiq, OpenRegister and the extension, runs the Chromium capture and a Firefox capture of the popup and a fill, and uploads the media as an artifact. * docs(extension): show the browser extension with screenshots and videos Twenty-one captured images and videos at the matching steps of using.md, a passkeys section, and where to download the packages. * docs(extension): point the README at the capture
…low passed Ticks vault-favourites-tags-and-last-used 5.1 to 5.5 with the live-pass evidence of 4 October, archives the change into the new vault-list-organisation spec and sets rows vault-09, vault-10 and vault-24 built.
… a minimum whose kind is off (#1152) * fix(extension): space Copy and Open, hide Hours unless Custom, hide a minimum whose kind is off - popup.css: a label is display: block, which beat the hidden attribute, so the Hours field of a custom expiry (and every other hidden label) stayed on screen. [hidden] now wins. - popup.css: Copy and Open in a vault row had no padding and no gap, so they read as CopyOpen. They get room and a visible keyboard focus. - generator-view.js: the minimum of numbers or special characters shows only while that kind is on; the generator already ignored it. * docs(extension): recapture the vault, send, generator and pairing media Copy and Open now stand apart, the send form no longer shows Hours next to a 1 hour expiry, the generator hides the special-character minimum while special characters are off, and the pairing view no longer shows an empty account bar. * style(extension): prettier on the popup details test
Quality Report — ConductionNL/keepiq @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| test-l10n | ✅ | ||||
| format | ✅ | ||||
| check-l10n-js | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| composer | ✅ | ✅ 114/114 | |||
| npm | ✅ | ✅ 660/660 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ✅ | ||||
| lockfile sync | ✅ | ||||
| PHPUnit | ❌ | ||||
| Newman | ❌ | ||||
| Playwright | ❌ | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-10-04 18:22 UTC
Download the full PDF report from the workflow artifacts.
Quality Report — ConductionNL/keepiq @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| test-l10n | ✅ | ||||
| format | ✅ | ||||
| check-l10n-js | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| composer | ✅ | ✅ 114/114 | |||
| npm | ✅ | ✅ 660/660 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ✅ | ||||
| lockfile sync | ✅ | ||||
| PHPUnit | ❌ | ||||
| Newman | ❌ | ||||
| Playwright | ❌ | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-10-04 18:53 UTC
Download the full PDF report from the workflow artifacts.
…lt-favourites chore(openspec): archive favourites, tags and last used, whose live flow passed
… app (#1154) * test(mobile): write crypto vectors with the web app's own modules tests/vectors/generate-crypto-vectors.mjs writes envelope, field, Send, TOTP and passkey vectors into tests/vectors/crypto/ using src/crypto, src/send, src/totp, src/passkey and the extension's webauthn.js, with the real Argon2id. tests/vitest/crypto-vectors.spec.js re-reads them. Task 1.2 of clients-mobile-apps. * wip(mobile): scaffold the Gradle KMP project and the first crypto sources Work in progress, does not build yet: Gradle wrapper 8.14.3 (pinned with checksum), version catalog, settings that include Android only when an SDK is present, the shared module build script, and the common Encoding, Primitives and RSA field sources. Task 1.1 and 1.3 of clients-mobile-apps. * feat(mobile): crypto in the shared core, byte-compatible with the web app RSA-OAEP-4096/SHA-256 field chunks, the version 1 private-key envelope with PBKDF2-SHA256 600000, AES-256-GCM, Send payloads and the Argon2id password wrap, TOTP and ES256 passkey assertions, over expect/actual primitives: javax.crypto and Bouncy Castle on jvm and Android, cryptography-kotlin (Security framework, CryptoKit) on iOS. Argon2id on iOS is open as 1.3.1. The vectors written by the web app pass on the jvm target (19 tests), and the reverse direction is in place: the core writes kotlin-output.json and tests/vitest/crypto-vectors-kotlin.spec.js opens it with the web modules. Adds the placeholder Android app and iOS package, and the Mobile workflow. Tasks 1.1, 1.3 and 1.4 of clients-mobile-apps. * build(mobile): dependency verification metadata for every artifact gradle/verification-metadata.xml holds the sha256 of every artifact the build resolves, for jvm, Android and the iOS klibs, written by resolveAllDependencies with -Pkeepiq.android=true so a machine without an Android SDK can regenerate it. The macOS Kotlin/Native toolchain and aapt2 are added for the CI runners. A changed checksum fails the build. Ticks tasks 1.2 and 1.4 of clients-mobile-apps. * fix(mobile): REUSE licence text, macOS checksums and the runner's Android SDK - LICENSES/Apache-2.0.txt for the Gradle wrapper scripts (REUSE). - Three POM and module files the macOS runner resolves on the buildscript classpath and Linux does not; sha256 added after checking Maven Central's sha1. - Drop setup-android: its sdkmanager run failed, and ubuntu-latest already ships an SDK with ANDROID_HOME set.
…1155) * test(mobile): write crypto vectors with the web app's own modules tests/vectors/generate-crypto-vectors.mjs writes envelope, field, Send, TOTP and passkey vectors into tests/vectors/crypto/ using src/crypto, src/send, src/totp, src/passkey and the extension's webauthn.js, with the real Argon2id. tests/vitest/crypto-vectors.spec.js re-reads them. Task 1.2 of clients-mobile-apps. * wip(mobile): scaffold the Gradle KMP project and the first crypto sources Work in progress, does not build yet: Gradle wrapper 8.14.3 (pinned with checksum), version catalog, settings that include Android only when an SDK is present, the shared module build script, and the common Encoding, Primitives and RSA field sources. Task 1.1 and 1.3 of clients-mobile-apps. * feat(mobile): crypto in the shared core, byte-compatible with the web app RSA-OAEP-4096/SHA-256 field chunks, the version 1 private-key envelope with PBKDF2-SHA256 600000, AES-256-GCM, Send payloads and the Argon2id password wrap, TOTP and ES256 passkey assertions, over expect/actual primitives: javax.crypto and Bouncy Castle on jvm and Android, cryptography-kotlin (Security framework, CryptoKit) on iOS. Argon2id on iOS is open as 1.3.1. The vectors written by the web app pass on the jvm target (19 tests), and the reverse direction is in place: the core writes kotlin-output.json and tests/vitest/crypto-vectors-kotlin.spec.js opens it with the web modules. Adds the placeholder Android app and iOS package, and the Mobile workflow. Tasks 1.1, 1.3 and 1.4 of clients-mobile-apps. * build(mobile): dependency verification metadata for every artifact gradle/verification-metadata.xml holds the sha256 of every artifact the build resolves, for jvm, Android and the iOS klibs, written by resolveAllDependencies with -Pkeepiq.android=true so a machine without an Android SDK can regenerate it. The macOS Kotlin/Native toolchain and aapt2 are added for the CI runners. A changed checksum fails the build. Ticks tasks 1.2 and 1.4 of clients-mobile-apps. * feat(mobile): API client, offline store and sync in the shared core - KeepiqApi (Ktor) calls the server the way the browser extension does: Basic auth with the app password, OCS-APIRequest, no cookies, no redirects, https only, and an OCS statuscode of 400 or more is an error under HTTP 200. OkHttp on Android and jvm, NSURLSession on iOS with cookie storage switched off. - VaultStore (SQLDelight) keeps item ciphertext as the server sent it and seals names, URLs and folder names under the unlock key, with secure delete. SQLCipher with a Keystore-bound key on Android. - VaultSync follows vault-sync.js: the manifest on start, after a write and on request, a cheap check first on foreground and on the timer, and the suite id, unlockKeyEpoch and two-factor block compared on every sync. Tasks 1.5 and 1.7 of clients-mobile-apps; 1.6 partly, 1.6.1 added. * fix(mobile): REUSE licence text, macOS checksums and the runner's Android SDK - LICENSES/Apache-2.0.txt for the Gradle wrapper scripts (REUSE). - Three POM and module files the macOS runner resolves on the buildscript classpath and Linux does not; sha256 added after checking Maven Central's sha1. - Drop setup-android: its sdkmanager run failed, and ubuntu-latest already ships an SDK with ANDROID_HOME set.
Quality Report — ConductionNL/keepiq @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| test-l10n | ✅ | ||||
| format | ✅ | ||||
| check-l10n-js | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| composer | ✅ | ✅ 114/114 | |||
| npm | ✅ | ✅ 660/660 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ✅ | ||||
| lockfile sync | ✅ | ||||
| PHPUnit | ❌ | ||||
| Newman | ❌ | ||||
| Playwright | ❌ | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-10-04 19:32 UTC
Download the full PDF report from the workflow artifacts.
Quality Report — ConductionNL/keepiq @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| test-l10n | ✅ | ||||
| format | ✅ | ||||
| check-l10n-js | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| composer | ✅ | ✅ 114/114 | |||
| npm | ✅ | ✅ 660/660 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ✅ | ||||
| lockfile sync | ✅ | ||||
| PHPUnit | ❌ | ||||
| Newman | ❌ | ||||
| Playwright | ❌ | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-10-04 20:04 UTC
Download the full PDF report from the workflow artifacts.
…1159) * feat(mobile): generator with the organisation policy, sharing the web generator's cases Port of src/generator/generator.js to the shared core: the same options, policy clamp, refusals and order of random draws, the EFF word list compiled from the web module at build time. tests/vectors/generator/cases.json holds 156 outputs of the web generator for seeded random sources; the Kotlin commonTest and a vitest spec both run it. Task 3.5. * feat(mobile): vault, Send and clipboard logic in the shared core VaultRepository reads the vault through the offline store and sync, opens one item fresh (the store when offline), and writes items and folders in the web app's shapes, encrypted to the suite key on the device. A refusal, also one inside an HTTP 200 OCS envelope, comes back as refused; offline, every edit is refused before it is sent. Use-only copies never open their value. The store keeps the use-only, read-only and blocked flags. SendService creates, lists and deletes sends; OpenSendClient opens a Send link as the public page does. SensitiveClipboard clears a copy after the user's delay (default 60 seconds). VaultKeys is the small interface the unlock flow (task group 2) hands these screens. Tests: VaultRepositoryTest (17), SendServiceTest (6), SendLinkTest (5), SensitiveClipboardTest (4) on jvm. * feat(mobile): Android vault, item, generator and Send screens Thin Compose screens over the shared core (tasks 3.1 to 3.6): the vault list by folder with on-device search and the last-synced or offline note, item detail with show, copy and the TOTP countdown, create and edit with the type's fields, move and trash, folder create, rename and delete, the generator with the organisation policy, and Send create, list, delete and share. OpenSendActivity opens a Send link shared to the app or opened with it. Copies go through a sensitive clip (EXTRA_IS_SENSITIVE) that is cleared after the user's delay. English and Dutch strings. VaultApp takes a VaultSession from the unlock flow (task group 2) and is not wired into MainActivity yet. * feat(mobile): iOS vault, item, generator and Send screens SwiftUI screens in the KeepiqApp package over the shared core (tasks 3.1 to 3.6): vault list by folder with search and the sync note, item detail with show, copy and the TOTP countdown, create and edit with the type's fields, move and trash, folder create, rename and delete, the generator with the organisation policy, Send create, list, delete and ShareLink, and OpenSendView for a Send link. Copies are written local only with an expiration date. A password Send waits for Argon2id on iOS (task 1.3.1). English and Dutch strings, generated from the Android ones. Shared additions for Swift: MobileSession, ItemDraft.edited, GeneratorSettings.tryGenerate, SendResult accessors, and interfaces instead of function types in the clipboard API. * test(mobile): Compose screen tests on the JVM with Robolectric ScreensTest checks the item detail and generator through the semantics tree TalkBack reads: a password stays hidden until shown, Show and Copy name their field, a use-only copy offers neither, and the policy locks the digit switch and raises the length. A control that shows a use-only password fails it. Robolectric 4.16 and the Compose test artifacts are added to the dependency verification metadata; the Mobile workflow runs the tests. * docs(openspec): record group 3 progress in the mobile apps tasks * style(mobile): prettier on the generator vectors spec * fix(mobile): keep the iOS model's init free of main-actor isolation * feat(mobile): open a Send link handed to the iOS app; copy from the Android open dialog * test(mobile): vault index rules on every target * fix(mobile): iOS build: no clash with Combine's Scheduler and Cancellable, explicit view inits Swift resolved KeepiqShared.Scheduler to the KeepiqShared object, not the module, so the clipboard timer interfaces are now ClearScheduler and PendingClear. Views with private state get explicit initialisers, so they can be built from other files. * test(mobile): caching off keeps nothing on the device, and offline the vault needs a connection * fix(mobile): clearer Dutch label for the Send view limit * fix(mobile): the iOS item form loads once, so edits survive a return to it * style(mobile): prettier on the generator vectors script; regenerating gives the same cases * fix(mobile): simpler Swift expressions for the list and Send row details * fix(mobile): Swift names Kotlin sealed subclasses as nested types (OpenResult.Opened) * fix(mobile): MobileSession.online throws to Swift for a non-https server
…e2e harness (#1160) * feat(mobile): pairing, unlock, PIN and idle lock in the shared core Login Flow v2 with the 20-minute poll limit and the client name as User-Agent, manual app-password pairing with the apiVersion check, unpair with the app-password revoke and a local wipe, master-password unlock with the unlockBlocked gate, the PIN wrap of pin-unlock.js with five tries, and the idle policy with the organisation cap (clients-mobile-apps 2.1-2.6). * feat(mobile): Android pairing, unlock, biometric, PIN, auto-lock and unpair Compose screens for Login Flow v2 in a Custom Tab with the app-password fallback, master-password unlock with the two-factor block, biometric unlock through an AndroidKeyStore key that needs a strong biometric and is invalidated on enrolment, a PIN, the idle lock with the organisation cap and the screen-off lock, and unpair. An e2e build type trusts the test server's certificate for 10.0.2.2 only; instrumented tests drive the flows. * test(mobile): e2e server with a browser stand-in, iOS replay fixtures and a live core test mobile/e2e/server.mjs fronts the capture Nextcloud with https, signs in and grants a Login Flow v2 page as the user would, lists the device list through occ, records the answers the iOS simulator replays, and replays them. LiveServerTest runs the shared core against the real server: pair, unlock, unpair, and the old app password answers 401. * feat(mobile): Argon2id on iOS through the reference C code The reference Argon2 code (CC0 or Apache 2.0, tag 20190702) is compiled per iOS target into a static library and bundled through cinterop, as design D1 names it (task 1.3.1). The C build matches the known-answer vector on Linux; the iOS simulator tests run the password-Send vectors in CI. * feat(mobile): iOS pairing and unlock, and the mobile e2e workflow SwiftUI screens for Login Flow v2 in an ASWebAuthenticationSession with the app-password fallback, master-password unlock with the two-factor block, Face ID or Touch ID through a Keychain item with .biometryCurrentSet, a PIN, the idle lock with the organisation cap and the protected-data lock, and unpair. The project is an XcodeGen spec. mobile-e2e.yml runs the Android tests on API 34 and 28 emulators against the capture test server behind an https front, and the iOS UI tests on a simulator against the recorded replay, with screenshots and video as the mobile-media-android and mobile-media-ios artifacts. * fix(mobile): link libc++ for the static Kotlin framework in the iOS app * fix(mobile): reachable settings buttons in the iOS app; tick tasks 2.1, 2.2, 2.4 and 2.6 * fix(mobile): treat only a return from a covering browser as an abandoned sign-in The e2e run showed the address form again right after Sign in, with no message: the activity resumed without the browser ever covering it. Only a resume after onStop now counts as the user leaving the sign-in page. Also a theme without the action bar, which overlapped the edge-to-edge content, log lines for the login flow, and logcat in the e2e artifacts. * fix(mobile): Swift names for the Kotlin unlock gate, and compile the iOS app in the Mobile workflow Kotlin exports the nested UnlockGate.Ready and UnlockGate.Blocked under those Swift names. The Mobile ios job now builds the SwiftUI app too, and keeps the generated header as an artifact. * fix(mobile): Kotlin exports newKeepiqClient as doNewKeepiqClient; Android screenshots for the docs Objective-C reserves the new prefix for ownership, so Kotlin renames the function. The screenshots come from the green API 34 e2e run 37234801862. * fix(mobile): drop the app-wide tap gesture that swallowed Form button taps on iOS The iOS e2e video shows both UI tests typing the address and then tapping Sign in and Use an app password with no effect. Every action now restarts the idle time instead. * fix(mobile): the iOS replay answers Login Flow routes without index.php The recording links the poll endpoint as /login/v2/poll, so the app polled a path the replay did not know and got 401, which the iOS UI test showed as 'Signing in failed (401)'.
Quality Report — ConductionNL/keepiq @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| test-l10n | ✅ | ||||
| format | ✅ | ||||
| check-l10n-js | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| composer | ✅ | ✅ 114/114 | |||
| npm | ✅ | ✅ 660/660 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ✅ | ||||
| lockfile sync | ✅ | ||||
| PHPUnit | ❌ | ||||
| Newman | ❌ | ||||
| Playwright | ❌ | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-10-04 22:16 UTC
Download the full PDF report from the workflow artifacts.
Quality Report — ConductionNL/keepiq @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| test-l10n | ✅ | ||||
| format | ✅ | ||||
| check-l10n-js | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| composer | ✅ | ✅ 114/114 | |||
| npm | ✅ | ✅ 660/660 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ✅ | ||||
| lockfile sync | ✅ | ||||
| PHPUnit | ❌ | ||||
| Newman | ❌ | ||||
| Playwright | ❌ | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-10-04 22:49 UTC
Download the full PDF report from the workflow artifacts.
…t flows tested on Android and iOS (#1163) * wip(mobile): wire the vault screens into the unlocked state, lock forgets keys, seed and replay the demo vault * test(mobile): Android vault flows test compiles; helper to wait for text to go * test(mobile): vault flows on the emulators and the simulator, seeded demo vault, replay keeps the vault in memory * test(mobile): read the created login back through the API and open the Send in the app * fix(mobile): the store runs its pragma as a query and reads as locked once closed; work after a lock ends quietly * test(mobile): open a folder in both vault tests * test(mobile): decline the iOS save-password prompt before unlocking; no password content type on the master password * style(mobile): import order in AppState * test(mobile): wait for the copy snackbar before the add button * fix(mobile): the keyboard closes on save and on create link; the test hides it before taps low on the screen * fix(mobile): the copy snackbar shows above the add button * test(mobile): wait for the item form to close after save * test(mobile): leave the search and go back on iOS 26 too * test(mobile): confirm the trash as an action sheet or a popover * test(mobile): one iOS video per test class * test(mobile): take the generated password from the generator, not the masked field * fix(mobile): the iOS keyboard closes on save and on create link; the test waits for a target to be hittable * test(mobile): read back through the API on a thread of its own * test(mobile): dismiss the iOS slide-to-type tip * test(mobile): an iOS video over 3 minutes plays faster until it fits * test(mobile): decline the iOS save-password prompt after saving a login * docs(mobile): Android vault screenshots and video from the emulator run * test(mobile): tap the trash confirmation the dialog shows * docs(openspec): mobile vault tasks, what the emulator and simulator runs verify * test(mobile): bring an off-screen or covered iOS button into reach before tapping * test(mobile): scroll a lazy iOS form until its button exists * test(mobile): scroll to the trash button; tap the account and lock buttons of the tab on screen * fix(mobile): the iOS authenticator countdown shows the seconds left instead of a spinner * test(mobile): wait for an iOS field to be uncovered before typing * docs(mobile): iOS vault screenshots and video from the simulator run
Quality Report — ConductionNL/keepiq @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| test-l10n | ✅ | ||||
| format | ✅ | ||||
| check-l10n-js | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| composer | ✅ | ✅ 114/114 | |||
| npm | ✅ | ✅ 660/660 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ✅ | ||||
| lockfile sync | ✅ | ||||
| PHPUnit | ❌ | ||||
| Newman | ❌ | ||||
| Playwright | ❌ | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-10-05 08:35 UTC
Download the full PDF report from the workflow artifacts.
* wip(mobile): shared autofill matching, index and field detection Shared cases from the extension in tests/vectors/autofill; index hook in VaultRepository; one test expectation still being fixed. * feat(mobile): shared autofill matching with the extension's cases SiteMatch, Capture, AppLink, AssetLinks, FieldDetector, AutofillIndex, NeverSaveList and the index hub in shared/autofill. The extension's matcher, use-only rules and save classifier write tests/vectors/autofill/cases.json; AutofillVectorsTest (Kotlin) and autofill-vectors.spec.js (vitest, 39 tests) both pass on it. * feat(mobile): fill choices and the save rules for system autofill in the shared core AutofillChoices decrypts only matched items; AutofillSaver saves, updates or leaves a submitted login as the extension's classifier decides. AutofillSaverTest (6) with real RSA keys against a fake server. * feat(mobile): Keepiq as the Android autofill service KeepiqAutofillService finds user name, password and one-time-code fields, matches a trusted browser's web domain or the app by package and signing certificate plus Digital Asset Links, offers decrypted logins and codes when unlocked and one 'Unlock Keepiq' entry without names when locked. Save requests honour the never-save list; 'Never' adds to it. The index is sealed under a Keystore key, rebuilt on every refresh and cleared on unpair, a locking sync and when autofill is off. Strings in English and Dutch. * test(mobile): system autofill on the emulator against the test server SystemAutofillTest drives the test APK's native forms and a WebView page on the e2e front with UiAutomator: the locked entry, the unlock and pick, Digital Asset Links, the look-alike app, the one-time code, saving, Never, and a trashed login gone after the next sync. server.mjs serves the assetlinks file and the login page; android-run.sh runs the class with its own recording. * test(mobile): the autofill test app's activities in Java They run in the test APK's own process, which has no Kotlin runtime. SystemAutofillTest passes on a local API 34 emulator against the test server (168 s, 11 screenshots). * feat(mobile): per-site autofill files and identities for the iOS extension * feat(mobile): iOS AutoFill credential provider extension KeepiqAutofill: the logins for the asked sites from per-site sealed files in the shared container (never the whole vault), the unlock inside the sheet with the shared Keychain group, a fill without interaction while the extension's own unlock lasts, search, 'Add login', and the one-time code on the clipboard for 60 seconds. The app rebuilds the files and ASCredentialIdentityStore after each refresh and clears them on unpair, a locking sync and when Keepiq is not the provider. AutofillUITests runs the sheet inside the app against the replay, which now keeps a small vault in memory. * docs(mobile): task group 4 progress in tasks.md; lint the autofill vector scripts 4.2, 4.3 and 4.6 done with their verification; 4.1, 4.4 and 4.5 partly done, naming the manual checks that stay open. * fix(mobile): no Never button before Android 11; wait for focus in the iOS AutoFill test NEGATIVE_BUTTON_STYLE_NEVER exists from API 30; on API 28 setNegativeAction threw and every fill request failed. The iOS test taps until the field in the sliding sheet has keyboard focus, and the sheet focuses the master password field itself. * fix(mobile): iOS never offers to save Keepiq's own secrets; UI tests answer the system prompt iOS showed its "Save Password?" prompt after the app password and the master password were typed, offering to keep Keepiq's secrets in another password manager, and the prompt covered the AutoFill sheet in the UI test. The master password, app password and PIN fields are now typed as one-time codes, and the UI tests answer the prompt if iOS still shows it. * test(mobile): answer the iOS save prompt by position when it blocks a tap The prompt runs in a system process the UI test cannot query, so looking up its "Not Now" button found nothing. When the element a step needs exists but cannot be tapped, the test now taps where "Not Now" sits and tries again. * test(mobile): find the iOS save prompt by name, and lower the keyboard instead of tapping by position Tapping where "Not Now" sits could land on the keyboard. The UI tests now answer the prompt by its button, as the vault flow tests do, and when a step's element cannot be tapped they lower the keyboard with Return or scroll it into view. * test(mobile): answer the iOS save prompt right after connecting iOS offers to save the app password a moment after Connect. Keys typed while that prompt slid in were lost, so the master password went in short and the unlock was refused. The tests now wait up to four seconds for the prompt and answer it before typing the master password. * fix(mobile): the iOS add-login form does not invite Apple's strong password The AutoFill sheet's add-login fields were typed .username and .newPassword, so iOS offered its own strong password over the sheet and covered the Save button. The user types the site's existing password there and Keepiq stores it, so the fields no longer carry those content types. * fix(mobile): iOS index files fall back to the app's storage; the test shows the second sheet Without the app group container (an unsigned simulator build) the site files were never written, so the second AutoFill sheet had no login to list while the counts said one. The files now fall back to Application Support, the preview label reports the files on disk and whether they are shared, and the test waits for the first sheet to close, then shoots the second sheet before asserting. * fix(mobile): autofill uses the open vault's own keys after the vault wiring merge * fix(mobile): the autofill unlock screen keeps the form visible; a longer unlock wait in the test Since the vault wiring, unlocking opens the store and syncs first, so the unlock takes seconds. Meanwhile the form under the opaque unlock screen stopped, its fields turned invisible, and Android ended the autofill session: the chosen login came back after the session was gone (API 34 logcat: 'the response has expired', then no fill). The screen is now translucent. The e2e test waits up to 180 s for the vault on API 28 and says what the screen and message were when it does not open. * test(mobile): wait for the two-factor block to clear before the autofill tests The block test turns the organisation's two-factor policy on and removes it after. The web server can keep that setting cached for a while, and on API 28 the autofill test started inside that window, so its unlock was refused with the two-factor message. The script now waits until the suites answer stops reporting the block.
…1166) * ci(mobile): preview APK release workflow, signed from secrets, with a pull request dry run A mobile-v<x.y.z>-preview.<n> tag builds the Android release APK, signs it with the preview key from GitHub secrets, checks the signature and the published certificate fingerprint with apksigner, and publishes a GitHub pre-release with the APK and its SHA-256. A pull request touching the Android build runs the same build signed with a throwaway key, without a release. Release signing reads environment variables only and stays unsigned without them. * docs(mobile): user guide for the Android and iOS apps, with the preview install and fingerprint Adds docs/mobile/using.md with the committed e2e screenshots and videos, lists the mobile apps in FEATURES.md, and ticks tasks 6.4 (preview only) and 7.1. * docs(openspec): tick 6.4 for the preview release, store tracks stay open * docs(mobile): show the connect screenshot at phone width * docs(mobile): autofill on Android, now that it is merged Explains how to choose Keepiq as the autofill service, what a locked fill looks like, the save offer and Never, one-time codes, and that iOS follows with a signed build.
Quality Report — ConductionNL/keepiq @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| test-l10n | ✅ | ||||
| format | ✅ | ||||
| check-l10n-js | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| composer | ✅ | ✅ 114/114 | |||
| npm | ✅ | ✅ 660/660 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ✅ | ||||
| lockfile sync | ✅ | ||||
| PHPUnit | ❌ | ||||
| Newman | ❌ | ||||
| Playwright | ❌ | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-10-05 09:23 UTC
Download the full PDF report from the workflow artifacts.
Quality Report — ConductionNL/keepiq @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| test-l10n | ✅ | ||||
| format | ✅ | ||||
| check-l10n-js | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| composer | ✅ | ✅ 114/114 | |||
| npm | ✅ | ✅ 660/660 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ✅ | ||||
| lockfile sync | ✅ | ||||
| PHPUnit | ❌ | ||||
| Newman | ❌ | ||||
| Playwright | ❌ | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-10-05 10:11 UTC
Download the full PDF report from the workflow artifacts.
…AGES measured, R8 and per-ABI APKs (#1169) * test(mobile): does autofill need QUERY_ALL_PACKAGES? Try without it SystemAutofillTest fills forms in the test APK, which Android always shows to the app it instruments (AppsFilterImpl marks both visible to each other), so it cannot tell whether Keepiq sees an ordinary app. PackageVisibilityTest fills a separate APK (:android:otherapp) instead, with a control that the other app is hidden from Keepiq before the fill when the permission is absent. This commit drops QUERY_ALL_PACKAGES to run that test without it on API 34 and 28. * fix(mobile): no Save Password offer from the iOS item and Send forms Keepiq's own secret fields are typed as one-time codes, as in the AutoFill sheet, so iOS no longer offers to keep a password after an item is saved. The UI test now fails when the offer comes after a save, and still answers it so the rest of the run continues. * feat(mobile): reach autofill from Keepiq's own settings on Android The settings screen gets an Autofill part: whether Keepiq is the autofill service, Choose Keepiq for autofill (Android's own question, with the phone's settings as fallback) and the way to the never-save list. The user guide starts from there and keeps Android's settings as the other route. * build(mobile): shrink the Android release with R8 and split the APK per ABI The release build runs R8 with resource shrinking (names kept, for readable crash reports); debug and e2e builds stay unshrunk. With -Pkeepiq.abiSplits=true the build also makes one APK per processor type next to the universal one; the preview workflow publishes all five with their SHA-256 and lists their sizes in the run summary. The API 34 e2e job installs the shrunk release build after the tests and checks that it starts and stays up through an autofill request. * fix(mobile): keep QUERY_ALL_PACKAGES, autofill cannot name an app without it PackageVisibilityTest without the permission (mobile-e2e run 37290748291): on API 34 the other app was hidden from Keepiq before the fill, the lookup during the fill was BLOCKED in the AppsFilter log, and Keepiq offered nothing. API 28 has no filtering and filled. The permission is back, the manifest and design.md say why, and the test stays as the guard that an ordinary app gets filled. * docs(mobile): name the autofill task group in the settings screen comment * test(mobile): record the iOS save prompt after the item form instead of failing The one-time-code typing did not stop iOS from offering to save a password after Keepiq's item form: iOS reads the form by its fields. The UI test now answers the prompt and attaches a note, and task 3.2.1 keeps the problem open until a way is found that stops the offer.
Quality Report — ConductionNL/keepiq @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| test-l10n | ✅ | ||||
| format | ✅ | ||||
| check-l10n-js | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| composer | ✅ | ✅ 114/114 | |||
| npm | ✅ | ✅ 660/660 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ✅ | ||||
| lockfile sync | ✅ | ||||
| PHPUnit | ❌ | ||||
| Newman | ❌ | ||||
| Playwright | ❌ | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-10-05 11:24 UTC
Download the full PDF report from the workflow artifacts.
* feat(mobile): passkey creation, signing with the caller's hash and the vault side in the shared core The core creates ES256 passkeys as browser-extension/src/passkey/webauthn.js does (zero AAGUID, none attestation, 16-byte id, flags UP UV AT), signs over a clientDataHash the caller built, finds a passkey by rpId in the autofill index, writes a non-zero counter back and saves a new passkey as a passkey item. The vectors gain the extension's own registration, and the Kotlin output gains a core-made passkey that vitest signs with the extension's code. androidx.credentials 1.3.0 joins the app for the provider (task 5.1). * feat(mobile): Keepiq as the passkey and password provider in Android's Credential Manager (task 5.1) KeepiqCredentialProviderService answers BeginGet and BeginCreate for passkeys and passwords with androidx.credentials only. The caller comes from the system: a browser's origin counts when it is on the privileged allowlist with its certificate, an app's rpId must list it in assetlinks.json. A locked vault offers Unlock Keepiq; a request for another algorithm gets nothing. The autofill settings say passkeys need Android 14 before 14. PasskeyProviderTest drives it on the API 34 emulator from the test APK's own Credential Manager client: create, sign in, counter 4 to 5, locked, refused. * feat(mobile): passkeys in the iOS AutoFill extension (task 5.2) The extension answers iOS 17 passkey assertion and registration requests after its own unlock, signing with the shared core's ES256 over the clientDataHash iOS built for the rpId it verified, and declines a request that allows no ES256. Passkey identities (rpId, user name, credential id, user handle) go into ASCredentialIdentityStore with every index rebuild. PasskeyUITests drives the extension's passkey screens and calls inside the app against the replay; ios-run.sh now also runs AutofillUITests, which the group 4 change added but the script never ran. * test(mobile): the dependency check names credentials-play-services-auth, and the DER reader's helper is renamed * test(mobile): the committed Kotlin vector output with the core-made passkey (task 5.3) Written by KotlinVectorsWriterTest in the Mobile run on this branch. * style(mobile): import order and Prettier in the cross-client passkey test * test(mobile): the passkey client tags each call, so the test never reads the previous answer On API 34 the locked sign-in read the counter step's answer (challenge seed 25 for 26) before the new client activity replaced it: no sheet screenshot, answer in the same instant. Create, sign-in and the counter 4 to 5 step had passed. Each call now carries a run id the status line starts with. * docs(mobile): passkeys in the user guide, and the group 5 task notes Android passkeys work on the emulator (PasskeyProviderTest, API 34); iOS says it comes with the signed build. * test(mobile): the passkey preview reads an attestation of exactly 178 bytes A none attestation with a 16-byte id is 30 + 148 bytes; the preview's guard asked for more and showed "registration unreadable" while the passkey was saved, on disk and in the identity store (the xcresult of run 37296407888).
Quality Report — ConductionNL/keepiq @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| test-l10n | ✅ | ||||
| format | ✅ | ||||
| check-l10n-js | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| composer | ✅ | ✅ 114/114 | |||
| npm | ✅ | ✅ 660/660 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ✅ | ||||
| lockfile sync | ✅ | ||||
| PHPUnit | ❌ | ||||
| Newman | ❌ | ||||
| Playwright | ❌ | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-10-05 12:23 UTC
Download the full PDF report from the workflow artifacts.
…te dialog The login now sits directly below the type select, before the value (or card / identity) fields, instead of below the URL.
…ove-value fix(secrets): show the login field above the secret value in the create dialog
Quality Report — ConductionNL/keepiq @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| test-l10n | ✅ | ||||
| format | ✅ | ||||
| check-l10n-js | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| composer | ✅ | ✅ 114/114 | |||
| npm | ✅ | ✅ 660/660 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ✅ | ||||
| lockfile sync | ✅ | ||||
| PHPUnit | ❌ | ||||
| Newman | ❌ | ||||
| Playwright | ❌ | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-10-05 15:02 UTC
Download the full PDF report from the workflow artifacts.
Dependabot reports DOMPurify below 3.4.16 as vulnerable, and the app's own lockfile resolved 3.4.15. The range now starts at 3.4.16 and the lockfile resolves it; no other package changes.
Quality Report — ConductionNL/keepiq @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| test-l10n | ✅ | ||||
| format | ✅ | ||||
| check-l10n-js | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| composer | ✅ | ✅ 114/114 | |||
| npm | ✅ | ✅ 660/660 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ✅ | ||||
| lockfile sync | ✅ | ||||
| PHPUnit | ❌ | ||||
| Newman | ❌ | ||||
| Playwright | ❌ | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-10-05 16:34 UTC
Download the full PDF report from the workflow artifacts.
Automated PR to sync development changes to beta for beta release.
Merging this PR will trigger the beta release workflow.