Skip to content

Release: merge development into beta - #834

Open
github-actions[bot] wants to merge 333 commits into
betafrom
development
Open

github-actions[bot] wants to merge 333 commits into
betafrom
development

Conversation

@github-actions

Copy link
Copy Markdown
Contributor

Automated PR to sync development changes to beta for beta release.

Merging this PR will trigger the beta release workflow.

Reminder: Add a major, minor, or patch label to this PR to control the version bump. Default is patch.

Comment on lines +18 to +43
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
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
… methods

Version001004Date20261002161000 and 0.3.4-unstable.20261002161000, later
than #915's 20261002160000. The Hydra gates pass (83 of 83 ran).

Refs #791
…#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
Both purposes kept: abort-migration (#891) and approve-device. Proofs are
single-use through the database now (#891); the approve route rides on
that unchanged. l10n is the union; info.xml keeps 20261002163000.
@github-actions

github-actions Bot commented Oct 4, 2026

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/keepiq @ e474e45

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
@github-actions

github-actions Bot commented Oct 4, 2026

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/keepiq @ 60a8e6a

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.

@github-actions

github-actions Bot commented Oct 4, 2026

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/keepiq @ 8a4691b

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.

rubenvdlinde and others added 3 commits October 4, 2026 21:15
…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.
@github-actions

github-actions Bot commented Oct 4, 2026

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/keepiq @ 9660943

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.

@github-actions

github-actions Bot commented Oct 4, 2026

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/keepiq @ bfacd21

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)'.
@github-actions

github-actions Bot commented Oct 4, 2026

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/keepiq @ b63cf61

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.

@github-actions

github-actions Bot commented Oct 4, 2026

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/keepiq @ 39a9aca

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
@github-actions

github-actions Bot commented Oct 5, 2026

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/keepiq @ 68ac1d6

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.
@github-actions

github-actions Bot commented Oct 5, 2026

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/keepiq @ cea39b7

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.

@github-actions

github-actions Bot commented Oct 5, 2026

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/keepiq @ bfc3601

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.
@github-actions

github-actions Bot commented Oct 5, 2026

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/keepiq @ 466849f

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).
@github-actions

github-actions Bot commented Oct 5, 2026

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/keepiq @ 6e2d8de

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
@github-actions

github-actions Bot commented Oct 5, 2026

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/keepiq @ ca7c26f

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.
@github-actions

github-actions Bot commented Oct 5, 2026

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/keepiq @ 4f8a871

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.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants