Repository navigation
Release: merge beta into main - #1204
Merged
Merged
Conversation
Both sides kept: the fill sends the tab host (#934) and the use-only flag, and a use-only copy is checked against that same host; the notifier runs its renderers as a list, so the emergency renderer (#931) and the access-end renderer both stay and prepare() stays simple; SecretService keeps the spec tag and #931's suppression; l10n is the union; info.xml keeps 20261002161000.
…t or face unlock (#921) * 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 * feat(extension): idle lock maximum endpoint and per-client passkey credentials GET /api/v1/extension/policy returns the administrator's maximum idle lock delay for the browser extension (default 240 minutes). Passkey credentials gain client_kind and rp_id, and the login options are scoped to the asking client, so the web app never gets an extension credential and the reverse. Refs #784 * feat(extension): several accounts, per-account locks, and a sender check on worker messages The extension holds up to five paired accounts, each with its own key, idle timer and match cache. The old single pairing becomes the first account. Matching, filling, the one-time code and saving use the active account, and the worker refuses a fill whose id did not come from the active account's own match for the current site. Only the four content-script messages are accepted from a tab; everything else needs an extension page as sender. The idle delay is per account, capped at the organisation maximum read at each unlock. Refs #784 * feat(extension): fingerprint or face unlock, account switcher and idle delay settings The unlock window enrols a platform passkey with PRF for one account (the extension's own origin as relying party, user verification required) and unlocks with it: the raw unlock key is unwrapped in the window and handed to the worker, which imports the key and drops the bytes. The server gets only the wrapped key, the PRF salt and the credential metadata. The option shows only where a user-verifying platform authenticator exists. The popup header switches between accounts and shows each one's lock state; a settings view picks the idle delay, with delays above the organisation's maximum shown as unavailable. Refs #784 * feat(extension): admin idle maximum field, extension passkeys in the web list; archive the change The admin settings get a Browser extension section for the longest idle delay users may pick. The web app's passkey list labels extension credentials, which the owner revokes there as any other. The change clients-extension-unlock-lock-and-accounts is archived: its spec deltas are synced into openspec/specs and the parity rows clients-06, clients-21 and crypto-09 are set to built. Closes #784 * chore(quality): reasoned complexity suppression on the admin settings service, @SPEC on the extension section
…nt-libraries-and-ci
Keeps both notifier families (emergency and device approval) in the renderer list, both App.vue mounts, info.xml at 163000, and adds device_approvals to the backup table registry.
…and-ci feat: client libraries for Go, Python and TypeScript, GitHub Action and GitLab CI template
…w-device-approval # Conflicts: # appinfo/info.xml # l10n/be.js # l10n/be.json # l10n/bg.js # l10n/bg.json # l10n/bs.js # l10n/bs.json # l10n/ca.js # l10n/ca.json # l10n/cs.js # l10n/cs.json # l10n/da.js # l10n/da.json # l10n/de.js # l10n/de.json # l10n/el.js # l10n/el.json # l10n/en.js # l10n/en.json # l10n/es.js # l10n/es.json # l10n/et.js # l10n/et.json # l10n/fi.js # l10n/fi.json # l10n/fr.js # l10n/fr.json # l10n/ga.js # l10n/ga.json # l10n/hr.js # l10n/hr.json # l10n/hu.js # l10n/hu.json # l10n/is.js # l10n/is.json # l10n/it.js # l10n/it.json # l10n/lb.js # l10n/lb.json # l10n/lt.js # l10n/lt.json # l10n/lv.js # l10n/lv.json # l10n/mk.js # l10n/mk.json # l10n/mt.js # l10n/mt.json # l10n/nb.js # l10n/nb.json # l10n/nl.js # l10n/nl.json # l10n/pl.js # l10n/pl.json # l10n/pt.js # l10n/pt.json # l10n/rm.js # l10n/rm.json # l10n/ro.js # l10n/ro.json # l10n/ru.js # l10n/ru.json # l10n/sk.js # l10n/sk.json # l10n/sl.js # l10n/sl.json # l10n/sq.js # l10n/sq.json # l10n/sr.js # l10n/sr.json # l10n/sv.js # l10n/sv.json # l10n/tr.js # l10n/tr.json # l10n/uk.js # l10n/uk.json # src/views/settings/Settings.vue
…w-device-approval
The extension's handlers moved to router.js (#921), so the use-only match filter, fill refusal, owner report and save-prompt block move there too. info.xml goes to 181000, past development's 180000.
…ackend)
Register five concrete admin settings classes (General, Policies,
Applications and machine access, People and offboarding, Audit and
compliance) in Keepiq's own container, so a Nextcloud delegation of a
class satisfies the #[AuthorizedAdminSetting] guard that names it.
Split the admin settings endpoint per area (decision of 2 Oct): one GET
and one PUT per area under /api/settings/admin/{general,policies,
applications,audit}, each guarded by its own area class and writing only
that area's keys; a key of another area answers 400. Version and trash
retention belong to Policies. Move the attribute guards and the inline
isAdmin checks to AdminAreaAuthorizer::holds(), and route admin handover
and team offboarding through the People area (vault_admin stays an alias).
Refs #774
…export (#936) * feat(siem): connector columns on SIEM sinks Version001004Date20261002170000 adds format (default json), credential_enc and connector_options to keepiq_siem_sinks; app version 0.3.4-unstable.20261002170000. SiemSink serializes format, connectorOptions and hasCredential, never the credential. Refs #782 * feat(siem): accept Splunk HEC and Sentinel sinks and CEF syslog SiemSinkService accepts splunk_hec and sentinel, requires https:// endpoints and a credential for them, validates connector options per type (Sentinel tenant, client, data collection endpoint and rule required; stream and authority host defaulted; https authority), and allows format cef on syslog only. The credential is encrypted with ICrypto and write-only (blank keeps it). The sink update audit names the connector type. The controller passes format, credential and connectorOptions. Red before, green after: SiemSinkServiceConnectorTest. Refs #782 * feat(siem): JSON, CEF, Splunk HEC and Sentinel formatters Pure formatters under lib/Service/Siem read the payload only through PayloadView, which returns the eight known fields and drops every forbidden metadata key again. CEF escapes header and extension values and takes severity from a fixed category map. A guard test plants every forbidden key and an extra top-level field and fails if any formatter output carries them or any value that is not from the payload or a fixed vendor constant. Refs #782 * style(siem): named arguments and spec anchors in the formatters Refs #782 * feat(siem): Splunk HEC and Sentinel delivery, CEF over syslog SiemTransport sends a cef syslog sink's CEF line in the existing RFC 5424 frame and a json one byte for byte as before. Splunk HEC posts one event with Authorization: Splunk <token> and accepts only HTTP 200 with code 0. Sentinel gets a client-credentials token for scope https://monitor.azure.com//.default, holds it in the process for the run, posts one row to the data collection rule stream, accepts 204 and retries once with a new token after a 401. Every failure goes through the existing retry and dead-letter path; test-fire works for every connector. Tested over a real TCP socket and a mocked HTTP client. Refs #782 * feat(siem): connector picker on the SIEM section The admin form picks Splunk HTTP Event Collector, Microsoft Sentinel, CEF over syslog, Syslog JSON or Webhook JSON and shows only the fields that connector needs. The credential field is write-only: never prefilled, blank keeps the stored one. siemConnectors.js holds the mapping, validation and request body; vitest covers the field set per connector, the body, and the empty credential on edit. New strings in every catalogue. Refs #782 * test(siem): Playwright flow for a Splunk HEC sink and its test-fire Refs #782 * test(siem): do not depend on translated quote marks in the e2e Refs #782 * feat(siem): Sentinel template, Splunk sourcetype and connector docs integrations/siem/sentinel/keepiq-dcr.json creates KeepiqAudit_CL and the data collection rule with stream Custom-KeepiqAudit; a test keeps its columns equal to SentinelRowFormatter. integrations/siem/splunk holds props.conf for keepiq:audit. Both READMEs name the least privilege each credential needs; docs/siem-connectors.md covers every connector. Refs #782 * docs(openspec): archive audit-siem-vendor-connectors Ticks every task with its test, syncs the siem-vendor-connectors spec, points the @SPEC and @e2e anchors at it, and sets parity row audit-14 to built. Closes #782 * refactor(siem): request object and connector settings class SiemSinkController create() and update() read the sink fields through SiemSinkRequest instead of eleven router-bound arguments; the HTTP contract and defaults are unchanged (SiemSinkRequestTest). Connector validation moves from SiemSinkService into SinkConnectorSettings, with the Sentinel rules in their own method, and updateSink() delegates the generic fields to applyGeneralChanges(). SiemService::updateSink() declares the InvalidArgumentException the controller catches. Spec tags on the picker binding; prettier and eslint on the SIEM files. Refs #782
…t stopped the agent
…949) Four EFF words carry a hyphen (drop-down, felt-tip, t-shirt, yo-yo), so splitting a random default passphrase on '-' failed now and then. The default is checked with a fixed random source; the content checks use a space as separator.
Version001008Date20261002181000, so it sorts after #921's migration.
…on of 2 Oct) Design, spec and tasks now describe one settings route pair per area, the concrete area classes registered by Keepiq itself, one initial-state key and mount element per area, and version and trash retention under Policies. Tasks 1.1, 1.3, 2.1 to 2.5 and 3.1 to 3.3 are done; 1.2 and 3.4 need a live instance, 4.1 is due one minor release later. Refs #774
…se-only-and-expiring-shares # Conflicts: # l10n/be.js # l10n/be.json # l10n/bg.js # l10n/bg.json # l10n/bs.js # l10n/bs.json # l10n/ca.js # l10n/ca.json # l10n/cs.js # l10n/cs.json # l10n/da.js # l10n/da.json # l10n/de.js # l10n/de.json # l10n/el.js # l10n/el.json # l10n/en.js # l10n/en.json # l10n/es.js # l10n/es.json # l10n/et.js # l10n/et.json # l10n/fi.js # l10n/fi.json # l10n/fr.js # l10n/fr.json # l10n/ga.js # l10n/ga.json # l10n/hr.js # l10n/hr.json # l10n/hu.js # l10n/hu.json # l10n/is.js # l10n/is.json # l10n/it.js # l10n/it.json # l10n/lb.js # l10n/lb.json # l10n/lt.js # l10n/lt.json # l10n/lv.js # l10n/lv.json # l10n/mk.js # l10n/mk.json # l10n/mt.js # l10n/mt.json # l10n/nb.js # l10n/nb.json # l10n/nl.js # l10n/nl.json # l10n/pl.js # l10n/pl.json # l10n/pt.js # l10n/pt.json # l10n/rm.js # l10n/rm.json # l10n/ro.js # l10n/ro.json # l10n/ru.js # l10n/ru.json # l10n/sk.js # l10n/sk.json # l10n/sl.js # l10n/sl.json # l10n/sq.js # l10n/sq.json # l10n/sr.js # l10n/sr.json # l10n/sv.js # l10n/sv.json # l10n/tr.js # l10n/tr.json # l10n/uk.js # l10n/uk.json
Version001009Date20261002182000 and 0.3.4-unstable.20261002182000; the previous merge had kept development's 180000, which would skip the upgrade.
…ped-roles # Conflicts: # appinfo/info.xml # l10n/be.js # l10n/be.json # l10n/bg.js # l10n/bg.json # l10n/bs.js # l10n/bs.json # l10n/ca.js # l10n/ca.json # l10n/cs.js # l10n/cs.json # l10n/da.js # l10n/da.json # l10n/de.js # l10n/de.json # l10n/el.js # l10n/el.json # l10n/en.js # l10n/en.json # l10n/es.js # l10n/es.json # l10n/et.js # l10n/et.json # l10n/fi.js # l10n/fi.json # l10n/fr.js # l10n/fr.json # l10n/ga.js # l10n/ga.json # l10n/hr.js # l10n/hr.json # l10n/hu.js # l10n/hu.json # l10n/is.js # l10n/is.json # l10n/it.js # l10n/it.json # l10n/lb.js # l10n/lb.json # l10n/lt.js # l10n/lt.json # l10n/lv.js # l10n/lv.json # l10n/mk.js # l10n/mk.json # l10n/mt.js # l10n/mt.json # l10n/nb.js # l10n/nb.json # l10n/nl.js # l10n/nl.json # l10n/pl.js # l10n/pl.json # l10n/pt.js # l10n/pt.json # l10n/rm.js # l10n/rm.json # l10n/ro.js # l10n/ro.json # l10n/ru.js # l10n/ru.json # l10n/sk.js # l10n/sk.json # l10n/sl.js # l10n/sl.json # l10n/sq.js # l10n/sq.json # l10n/sr.js # l10n/sr.json # l10n/sv.js # l10n/sv.json # l10n/tr.js # l10n/tr.json # l10n/uk.js # l10n/uk.json # lib/Controller/SiemSinkController.php # lib/Service/AdminSettingsService.php # src/views/settings/Settings.vue
* feat(offline): baseUpdatedAt precondition on secret update and delete, offline edits setting
PUT and DELETE /api/v1/secrets/{id} accept an optional baseUpdatedAt. When
the secret changed since, nothing is written and the answer is 409 with the
current row. The admin setting offline_edits_enabled (default off) travels in
the offline manifest as offlineEditsEnabled.
Refs #785
* style(offline): named argument for StaleWriteException
* feat(offline): sealed edit queue store with coalescing
A queue store in the offline IndexedDB database (schema version 2). Each
entry's values and index fields are sealed to the owner's own certificate
with the hybrid envelope; only ids, the operation, the base version, the
queue time, the suite and the status are plain. Changes to one secret
coalesce. The snapshot now carries each secret's updatedAt and the
administrator's offline edits rule.
Refs #785
* feat(offline): queue offline edits and replay them with a fresh fan-out
While served from the cache with offline edits on, the secret store queues
creates, edits, moves and deletes instead of calling the server, and the
cached view shows them at once. Back online and unlocked, the queue replays
oldest first: owner rows through POST, PUT and DELETE with the base version,
then the recipient fan-out with shares and certificates fetched at that
moment. A 409 waits for the user's choice, 403 and 404 go to a failed list,
423 and network errors stop and keep the rest, and a failed recipient sync
retries only that step. A compromise recovery waits until the queue is empty.
Refs #785
* style(offline): lint
* feat(offline): offline edit screens, sync panel, conflict dialog and admin switch
Offline with offline edits on, the secret sidebar offers edit, move and
delete, New secret stays available, and queued changes show as not synced
yet. Sharing and attachments stay online-only and say so. A sync panel
under the banner shows how many changes wait, lets the user settle a
conflict (both versions decrypted in the browser), copy or discard a
refused change, and reopen changes made before a key change elsewhere with
the previous master password. Leaving the page with pending changes asks
first. The admin offline cache section gets the offline edits switch.
Refs #785
* docs(openspec): archive clients-offline-edits
Spec deltas synced: the new offline-edit-queue capability, and the
strictly read-only requirement removed from offline-readonly-cache. Parity
row clients-19 set to built. Code tags that named the removed requirement
point at the online-only requirement that replaces it.
Closes #785
* refactor(offline): OfflineEditGuard, a separate update controller, @SPEC on the queue methods
The baseUpdatedAt precondition moves out of SecretService into a small
OfflineEditGuard, used by update() and by findOwned() for the trash path,
so SecretService keeps its method count and the trash service needs no
static call. PUT /api/v1/secrets/{id} moves to SecretUpdateController,
which reads the changed fields from the request, so SecretController's
coupling and update()'s parameter list stay within the limits. The offline
switches leave getAdminSettings() for their own reader. Seven queue methods
get their @SPEC tag.
Refs #785
…w-device-approval # Conflicts: # l10n/be.js # l10n/be.json # l10n/bg.js # l10n/bg.json # l10n/bs.js # l10n/bs.json # l10n/ca.js # l10n/ca.json # l10n/cs.js # l10n/cs.json # l10n/da.js # l10n/da.json # l10n/de.js # l10n/de.json # l10n/el.js # l10n/el.json # l10n/en.js # l10n/en.json # l10n/es.js # l10n/es.json # l10n/et.js # l10n/et.json # l10n/fi.js # l10n/fi.json # l10n/fr.js # l10n/fr.json # l10n/ga.js # l10n/ga.json # l10n/hr.js # l10n/hr.json # l10n/hu.js # l10n/hu.json # l10n/is.js # l10n/is.json # l10n/it.js # l10n/it.json # l10n/lb.js # l10n/lb.json # l10n/lt.js # l10n/lt.json # l10n/lv.js # l10n/lv.json # l10n/mk.js # l10n/mk.json # l10n/mt.js # l10n/mt.json # l10n/nb.js # l10n/nb.json # l10n/nl.js # l10n/nl.json # l10n/pl.js # l10n/pl.json # l10n/pt.js # l10n/pt.json # l10n/rm.js # l10n/rm.json # l10n/ro.js # l10n/ro.json # l10n/ru.js # l10n/ru.json # l10n/sk.js # l10n/sk.json # l10n/sl.js # l10n/sl.json # l10n/sq.js # l10n/sq.json # l10n/sr.js # l10n/sr.json # l10n/sv.js # l10n/sv.json # l10n/tr.js # l10n/tr.json # l10n/uk.js # l10n/uk.json # lib/Service/AdminSettingsService.php
feat: Kubernetes operator for Keepiq application secrets (stacked on #914)
…et-sync-and-rotation-runner
…red by their own registrar Move the per-area settings routes to AdminAreaSettingsController (with a user rate limit on each write), report adminAreas from SettingsController instead of SettingsService, register the area classes in AdminAreaRegistrar, and name areas through AdminAreaAuthorizer constants where a controller was over the coupling limit. Place development's new extension idle setting in the Policies area. Refs #774
…ped-roles # Conflicts: # l10n/be.js # l10n/be.json # l10n/bg.js # l10n/bg.json # l10n/bs.js # l10n/bs.json # l10n/ca.js # l10n/ca.json # l10n/cs.js # l10n/cs.json # l10n/da.js # l10n/da.json # l10n/de.js # l10n/de.json # l10n/el.js # l10n/el.json # l10n/en.js # l10n/en.json # l10n/es.js # l10n/es.json # l10n/et.js # l10n/et.json # l10n/fi.js # l10n/fi.json # l10n/fr.js # l10n/fr.json # l10n/ga.js # l10n/ga.json # l10n/hr.js # l10n/hr.json # l10n/hu.js # l10n/hu.json # l10n/is.js # l10n/is.json # l10n/it.js # l10n/it.json # l10n/lb.js # l10n/lb.json # l10n/lt.js # l10n/lt.json # l10n/lv.js # l10n/lv.json # l10n/mk.js # l10n/mk.json # l10n/mt.js # l10n/mt.json # l10n/nb.js # l10n/nb.json # l10n/nl.js # l10n/nl.json # l10n/pl.js # l10n/pl.json # l10n/pt.js # l10n/pt.json # l10n/rm.js # l10n/rm.json # l10n/ro.js # l10n/ro.json # l10n/ru.js # l10n/ru.json # l10n/sk.js # l10n/sk.json # l10n/sl.js # l10n/sl.json # l10n/sq.js # l10n/sq.json # l10n/sr.js # l10n/sr.json # l10n/sv.js # l10n/sv.json # l10n/tr.js # l10n/tr.json # l10n/uk.js # l10n/uk.json
…se-only-and-expiring-shares # Conflicts: # l10n/be.js # l10n/be.json # l10n/bg.js # l10n/bg.json # l10n/bs.js # l10n/bs.json # l10n/ca.js # l10n/ca.json # l10n/cs.js # l10n/cs.json # l10n/da.js # l10n/da.json # l10n/de.js # l10n/de.json # l10n/el.js # l10n/el.json # l10n/en.js # l10n/en.json # l10n/es.js # l10n/es.json # l10n/et.js # l10n/et.json # l10n/fi.js # l10n/fi.json # l10n/fr.js # l10n/fr.json # l10n/ga.js # l10n/ga.json # l10n/hr.js # l10n/hr.json # l10n/hu.js # l10n/hu.json # l10n/is.js # l10n/is.json # l10n/it.js # l10n/it.json # l10n/lb.js # l10n/lb.json # l10n/lt.js # l10n/lt.json # l10n/lv.js # l10n/lv.json # l10n/mk.js # l10n/mk.json # l10n/mt.js # l10n/mt.json # l10n/nb.js # l10n/nb.json # l10n/nl.js # l10n/nl.json # l10n/pl.js # l10n/pl.json # l10n/pt.js # l10n/pt.json # l10n/rm.js # l10n/rm.json # l10n/ro.js # l10n/ro.json # l10n/ru.js # l10n/ru.json # l10n/sk.js # l10n/sk.json # l10n/sl.js # l10n/sl.json # l10n/sq.js # l10n/sq.json # l10n/sr.js # l10n/sr.json # l10n/sv.js # l10n/sv.json # l10n/tr.js # l10n/tr.json # l10n/uk.js # l10n/uk.json # src/components/SecretDetailSidebar.vue
… fill (replaces #937) (#950) * build(extension): a package per store target with the tag version; drop the unused scripting permission build.mjs takes --target chrome|firefox and --outdir, and EXTENSION_VERSION sets the manifest version. The build clears only its own package folder. The manifest loses scripting, which nothing calls, and its description is rewritten as store copy. Tests check both manifests, that two builds are byte-identical, and that every permission has a call site. Refs #783 (cherry picked from commit 2795d51) * feat(extension): version handshake with the Keepiq server The pair response carries the server's app version. The extension stores it per account, checks it again at unlock, and with a server older than its minimum shows an update message instead of the vault and sends no match request. Refs #783 (cherry picked from commit fb74568) * feat(extension): fill the one-time code on the step after the login After a login fill whose site has a totp secret and no code field on the page, the worker keeps a one-shot intent in chrome.storage.session: tab id, registrable domain, totp secret id and an expiry five minutes out, no seed and no code. The content script reports a code field that appears later (on load or through a throttled MutationObserver), once per field. The worker fills it only for that tab and site, before the expiry, while the vault is unlocked, in the reporting frame, and deletes the intent first. Lock and unpair clear every intent. Refs #783 (cherry picked from commit ab09c48) * ci(extension): release workflow, store credentials, privacy policy and rollout docs extension-release.yml runs the extension tests, builds each target twice and fails when the builds differ, lints the Firefox package and packs both with a source archive. On an extension-v tag a job in the protected extension-stores environment submits to the Chrome Web Store, Firefox Add-ons and Edge Add-ons and attaches the packages to a GitHub release. docs/browser-extension holds the privacy policy, one line per permission, the enterprise rollout and the release steps with placeholder credentials. Refs #783 Refs #756 (cherry picked from commit ba76b9c) * test(extension): point the next-step code fill test at the change's own requirement The requirement lives in the clients-extension-store-release spec delta until that change is archived, so gate-46 could not resolve the main-spec anchor.
…compares ApplicationSecretsController reached phpmd's class complexity threshold with the If-Match precondition. The comparison (any listed strong tag or *) moves to MachineSecretEnvelopeService::ifMatchHolds(), next to etag(); the controller keeps the 412 response. Unit test covers current, *, a list, stale and weak tags.
…et-sync-and-rotation-runner
6.2 with OpenRegister disabled: Nextcloud pages 200, health 200 ok, metrics 200 for an admin, preferences round-trip, settings/load 404, WASM CSP still sent; the UI checks were done in the browser. The cleanup step removed the doriath/keepiq registers, the example schema and its empty data table (51->49 registers, 191->190 schemas), and a second run removed nothing. 6.3 with OpenRegister enabled: the flows API answers 200 and the MCP endpoint lists exactly keepiq.expiryReport, listEntries, rotationStatus. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The README now lists Keepiq's own monitoring and preferences endpoints with their callers and responses, and the demo-environment page says OpenRegister is required by the demo suite, not by Keepiq. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
- appinfo/info.xml <version> moves to 0.3.4-unstable.20261005160000. Without it `occ upgrade` answers "No upgrade required" and the new RemoveLegacyRegisterRows repair step never runs on existing instances (hydra gate-110). - StaticRouteTableTest no longer assumes OpenRegister is absent: CI installs it. The table test now holds on either instance shape. - Prettier formatting for App.vue, standalone-shell.spec.ts and navEntries.spec.js. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Legacy-row cleanup (RemoveLegacyRegisterRows): - Another app's register that lists a removed schema loses only that entry; every other entry is kept exactly as stored (no intval rewrite). - Rows are deleted first and empty data tables dropped last, so a failure partway never leaves a schema pointing at a dropped table. - A keepiq/doriath register is kept while objects are stored under it. - Keepiq's openregister_configurations entries are removed once nothing they list still exists, also on an instance an earlier run already cleaned up (verified on the dev instance: configurations 9 and 14). Shell: - Metrics keeps serving keepiq_info and keepiq_up when the suite count fails, as the AppHost engine did; the series is left out and logged. - Health no longer reports a failed filesystem when only removing the probe file fails. - One isAppEnabled/currentAppsWebRoots helper in navEntries.js decides app presence for the menu gates and the AI companion. - The unused `openregisters` settings flag, its store field and the IAppManager it needed are removed. - docker-compose.yml says what the unconditional ../openregister mount really does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Blockers from the PR review, each verified in OpenRegister's source: - Data tables are found by their physical name in the database catalogue (as OpenRegister's SchemaDeletionService does), not from a register's schemas list, which can lose entries. When the catalogue cannot be read, nothing is removed. - Emptiness counts every data table of a schema or register, plus the legacy openregister_objects table only while it still exists (OpenRegister drops it after its blob migration). - Registers are selected by `application`, never by slug: OpenRegister's key is (organisation, application, slug). A register without an application is never deleted. Concerns and minors: - All row changes run in one transaction with rollback; empty tables are dropped after the commit, each after a final row count. - Every removal is logged first with table, id, slug, application and the counts that justified it. - The planning moved into a read-only LegacyRegisterPlanner (phpmd class complexity); the step applies the plan. - Tests cover a lost register link, a Keepiq register with populated tables, another app's keepiq-slug register, a failure followed by a converging run, and a row written before the drop. Mutation-checked. - Health checks the temp directory without writing a file; preference values are capped at 4 KB; the README recommends a dedicated, General-area-delegated account for Prometheus. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(mobile): fdroid and play flavours, free-software check and reproducible build in CI Tasks 6.1 and 6.2 of clients-mobile-apps. The flavours differ only in the update check, and neither has one: Play Core is not free software, and both stores update the app themselves. mobile-fdroid.yml fails on any non-free artifact in the fdroid or play release runtime classpath and builds the unsigned fdroid release APK twice, in two clean checkouts at different paths, and compares them byte for byte. Build tools pinned, vcsInfo off. The preview, e2e and unit test workflows use the flavoured task names. * feat(mobile): store texts and screenshots for F-Droid and Google Play (draft) Task 6.3. English and Dutch title, short and full description and the first changelog in mobile/fastlane/metadata/android, with eight screenshots from docs/static/media/mobile. Draft for the product owner's review. * docs(mobile): privacy policy for the mobile apps, FEATURES.md to current state Task 7.3. docs/mobile/privacy.md says what the app sends where (your Nextcloud, the server of a Send link you open, a website's public assetlinks.json when another app asks for a login) and what it stores. using.md links it. * docs(mobile): developer README Task 7.2. Layout, building the shared core, Android and iOS, the crypto vectors, the e2e harness, the flavours and the F-Droid rules. * test(mobile): control, a Google Play Services dependency in the fdroid flavour Must make the free-software job of mobile-fdroid.yml fail and name the artifact. Reverted in the next commit. * Revert "test(mobile): control, a Google Play Services dependency in the fdroid flavour" The control has run (mobile-fdroid.yml run 37354683074); the fdroid flavour is free software again. * docs(openspec): tick mobile tasks 6.1, 6.2, 6.3 (draft), 7.2 and 7.3 with their CI runs
- AdminAreaSettingsTest::testInfoXmlRegistersEveryArea read info.xml
with simplexml_load_file(). Under the Nextcloud bootstrap in CI,
lib/base.php blocks libxml's external entity loader, which
simplexml_load_file() also uses to open the file, so it got false and
failed on iterator_to_array(null). It now parses the file contents,
as NextcloudFloorMatrixTest and ConnectionsDeclarationTest already do.
- admin-api.postman_collection.json declared only its auditor variables,
so all 23 requests went to the literal `{{baseUrl}}` (getaddrinfo
ENOTFOUND). It now declares baseUrl, noAuthBase, adminUser and
adminPass with the same values as the other two collections.
Both failures predate this branch (#1173 shows the same).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
phpunit.xml sets beStrictAboutCoverageMetadata and failOnRisky, so a test that runs a class its @Covers does not declare is risky, and a risky test fails the run. Ten tests did (the same ten on development), which was hidden while AdminAreaSettingsTest errored first: - ExtensionControllerTest uses AdminSettingsService and PasswordPolicyService. - SettingsControllerConnectionRefreshTest uses AdminAreaSettingsController. - SiemConnectionReportCallersTest uses Siem\SinkConnectorSettings. Each @uses names exactly the classes CI reported for that test. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
feat(app-shell): Keepiq runs without any other app
…nment Closes the gaps of #863/#864 left on development (keepiq#1189): - A share-target lookup that throws while resolving a copy's source is counted as a containment failure; the source owner was not warned but the cascade reported complete. - When revoking the other end of a migration throws, the named suite is already revoked: its containment now runs in a finally on the radius collected before any revoke. Before, it was skipped, and a retry collected after the revoke and lost the source owners. - The containment tally is audited as suite.compromise_contained, flagged incomplete when a step failed, instead of reaching only the response. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ment Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PARAM_DATE is deprecated since NC 31 and is defined as Types::DATETIME_MUTABLE, exactly what PARAM_DATETIME_MUTABLE is, so this is a pure rename. All bound values are \DateTime and the compared columns (keepiq_audit_log.occurred_at, keepiq_ca_certs.expires_at) are Types::DATETIME. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01M7DxSg7d7wqqZsUppLWf7J
…not refused Self-review of #1190: - A failure to revoke the other end or end the migration, after the named suite was revoked and contained, fell into forceRevoke's RuntimeException branch: a 403, a suite.revoke_refused (forbidden) audit for a revoked suite, and the containment tally dropped. It is now caught, counted as a failed step, audited as migrationEndFailed and answered with a 500 `migration_end_failed` that carries the tally. - A retry on a suite left revoked with its migration open only finishes the migration. Only a compromise force-revoke revokes a migrating suite, so its containment already ran; running it again warned everyone twice. - A failed source lookup no longer also attempts the outbound lookup, so it counts as one failure. - The admin force-revoke moved into AdminSuiteRevocationService; the controller had outgrown phpmd's class-length and parameter limits. - Tests pin the exact failure status, body and audit, assert no refusal is recorded, and cover a failed named revoke and a retry that fails again. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
refactor(nc35): replace deprecated IQueryBuilder::PARAM_DATE
…te containment Re-review of #1190: - The retry shortcut took "revoked with the migration still open" as proof that containment ran. An attempt can stop between the revoke and contain() (a throw after the status update, a killed worker, a failed audit write), and every later retry then skipped containment and answered success. The shortcut now requires a suite.compromise_contained audit entry for the suite since the migration started, with no failed containment step. No entry, a failed step or an unreadable trail runs the whole path again, the revoke included, so a revoke cascade that stopped is redone too. - The migration_end_failed message no longer claims containment when containment steps failed; the admin UI renders the result and cascade cards for that 500 instead of only the message. - A retry whose migration end fails again is recorded as suite.revoke_refused with reason code migration_end_failed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(mobile): give the apps Keepiq's lock as launcher and store icon Android: an adaptive icon (brand blue background, the lock from img/pwa-icon-maskable.svg as a VectorDrawable foreground inside the 66 dp safe zone, the same layer as the monochrome icon for themed icons), legacy PNG mipmaps, and android:icon/roundIcon on the application. Store: a 512 px icon.png for en-US and nl-NL in the fastlane metadata. iOS: an AppIcon asset catalog with one 1024 px icon. * fix(mobile): pad the phone screenshots to Google Play's 2:1 limit The screenshots were 2.22:1 and Play takes at most 2:1. Each one is padded evenly left and right, each padding row filled with that row's edge pixel, to 557x1100 (1-7) and 1214x2400 (8), 1.98:1. No content is cut, and the one set in the fastlane layout serves F-Droid and Play alike. * docs(openspec): note the store icon and Play-sized screenshots under task 6.3 * fix(mobile): draw the legacy launcher PNGs straight from img/pwa-icon.svg
…mpt after Keepiq's forms (tasks 1.6.1, 3.2.1) (#1184) * feat(mobile): encrypted offline store on iOS (task 1.6.1) SQLCipher Community Edition 4.19.0, built from the pinned release source (SHA-256 checked) into the KeepiqShared klib per iOS target, linked by the SQLDelight native driver instead of the system SQLite. The database key is 32 random bytes per account in a non-biometric Keychain item (AfterFirstUnlockThisDeviceOnly, through KeychainStorage). The store refuses to open when SQLCipher is missing (PRAGMA cipher_version), and a file whose key is gone starts again empty. Unpair deletes file and key; suite change and offline caching off empty it through VaultSync, as on Android. The iOS app opens its sessions through IosVaultSession with the store and sync; the lock and wrap deletion of a sync reach AppModel. Tests: the store on the iOS simulator (IosEncryptedStoreTest) and OfflineUITests, which reads the stored vault after the replay drops every connection (POST /__e2e/offline). * fix(mobile): no "Save Password?" after Keepiq's own iOS forms (task 3.2.1) iOS reads the user name and password fields when they leave the view hierarchy and offers to save what it finds (WWDC 2018 session 204: "Make sure to only clear the username and password fields after they've been removed from the view hierarchy. This way, we can read out the data and save it into credential."). The item form, the Send form and the AutoFill sheet's add-login form now empty their secret fields once the save went through, wait a moment for the fields to update, and only then leave. The fields stay SecureFields, so VoiceOver announces them as secure as before. VaultFlowsUITests.expectNoSavePasswordPrompt fails again when the prompt comes; AutofillUITests records whether it came after the add-login form. * test(mobile): opt in to the foreign API in the iOS store test * fix(mobile): MobileSession keeps its store to close it on lock * test(mobile): answer the save prompt before the offline test types * build(mobile): name each iOS simulator test in the log * test(mobile): the offline test taps until the field has focus, five tries
…proven records Third review pass of #1190: - The audit fake now asserts the exact lookup (this event, object type, suite and the migration's start, limit 1), and the migration fixture has a start, so dropping or changing a filter fails the tests. - A record without an integer failed count no longer passes as complete: {"migrationEndFailed": true} computed 1 - 1 = 0 and skipped containment. Found by the new data provider (no metadata, empty, a failed step, no count); all of them now contain again. - A migration without a start contains again instead of querying with no time bound. - AuditEntryMapperTest runs the real findFiltered query: an entry before the migration, another event type and another suite are excluded, and the newest match is returned. It skips without a database, like SecretRequestMapperTest, and runs in CI. - The store only treats a 500 as migration_end_failed; the store and controller docblocks no longer say the suite was contained. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nment fix(encryption-suites): count, always run and audit compromise containment
The 17 alerts GitHub Advanced Security reports on development (and on the development -> beta release PR #834): - actions/missing-workflow-permissions (12): the repository's default token is write-all. Every flagged workflow now declares its permissions: contents: read for the self-contained ones, contents: write only on the cli-release jobs that attach to a release, and for callers of the shared ConductionNL/.github workflows exactly the union the called jobs declare (release, sync-to-beta: contents + pull-requests write; documentation: contents + packages write; issue-triage, openspec-sync: issues write). - js/polynomial-redos (sdk/js): the trailing-slash strip of the caller's address backtracked quadratically on a long run of slashes; replaced by a loop, with a test for an address with trailing slashes. - js/incomplete-url-substring-sanitization (3, extension tests): the assertions compared URL prefixes, which also matched other hosts; they now compare the parsed origin. - js/insecure-randomness (e2e helper): the run-unique user name suffix uses crypto.randomInt instead of Math.random. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
fix(ci): resolve the open code-scanning alerts on development
The generic Reports entry leaves the footer for the settings foldout, which nextcloud-vue now labels Advanced.
…enable The push runs on development test Nextcloud stable32-34; pull requests only run stable35, so these failures never showed on a PR. - FederatedSharesMigrationTest and SiemConnectorColumnsMigrationTest mocked stdClass when ISchemaWrapper::getTable() declares no return type (Nextcloud 34 and older), and stdClass has no hasColumn(). They now use a plain recording table there, as ConsolidatedSchemaMigrationTest does. The migrations were fine: getTable() returns a Doctrine Table there. - The federation tests that use the OCM APIs of Nextcloud 33 skip on 32, where FederationRootService::isSupported() is false and federation is off by design. The skip uses the same check. - The standalone and two-instances setups hit a transient 404 on the first API call right after app:enable, once each on 4934a64 (and green on the other run of the same commit). They now wait for the API first. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The same three failed, retry included, on every E2E run since at least 4f942f7: - l10n-browser-catalogue picked the first entry whose value differs from its key as the t() probe. Plural entries (`_singular_::_plural_`) hold an array of forms, so they always qualified, and t() cannot resolve them. The probe now takes a plain string entry. - member-overview-offboarding expected "1 team folders."; the summary is pluralised now and says "1 team folder.". - vault-unlock walks every manifest route while locked. Each goto is a full page load, about 4.6 s a route in CI (measured from the trace), so the 17 routes ran out of the default timeout at route 15. The timeout now scales with the number of routes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With its timeout fixed, the locked-vault leak test reaches its assertion for the first time since device approval came to the lock screen (#1024), and finds two requests the lock screen itself makes: - device-approvals/status: checkSuite() asks, whenever online, whether to offer "Approve from another device". Answers {enabled}. - recovery/enrolment: with device approval on, DeviceApprovalRequest reads the user's own enrolment to decide whether to offer the administrator path. Answers {enrolled, policy}. Neither carries vault material. Run locally with Playwright against an instance with device approval enabled: all three specs fixed on this branch pass (10 passed, 1 skipped), and the development versions of the l10n and offboarding specs fail exactly as in CI. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
fix(ci): green PHPUnit on Nextcloud 32-34 and Playwright on development
Release: merge development into beta
Contributor
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-07 12:51 UTC
Download the full PDF report from the workflow artifacts.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #1189.
Release:
beta(1181e45, which includes #834) intomain(last updated 2026-09-29 by #828). 369 commits, about 150 merged PRs. Supported versions are unchanged: PHP 8.3+, Nextcloud 32–35. 13 new database migrations.Issues this closes
The PRs below merged into
development, which is not the default branch, so theirCloseslines closed nothing. Of the issues they claim to fix, only one is still open:The #691 beta-review issues (#858–#882) were already closed by hand. Two of their follow-ups stay open and are not fixed by this release: #1187 (GDPR deletion leaves recovery enrolments and sessions) and #1188 (Ephemeral Sends and federated shares survive a compromise containment).
Upgrade notes
vault_adminno longer grants the People and offboarding admin area (feat(admin-areas): remove the vault_admin alias and its notice #1138). A member of that group without a delegation loses the admin handover and team offboarding. Delegate the People and offboarding area to their group on Nextcloud's administration privileges page before upgrading.migration_end_failedinstead of 403 (fix(encryption-suites): count, always run and audit compromise containment #1190);What's new
Vault
occ(feat(backup): scheduled vault backups with verify and restore from occ #915, fix(backup): restore binds values by type on PostgreSQL, live checks #1029, fix(backup): restore switches maintenance mode on and off itself #1055).Sharing
Federation (share with other organisations over OCM, Nextcloud 33+)
Administration
/api/v1/admin(feat(admin-api): a versioned admin API under /api/v1/admin (stacked on #962) #966).Integrations and tooling
keepiq ssh-agentserves vault SSH keys (feat(cli): keepiq ssh-agent serves vault SSH keys #932, fix(cli): page the secret list and let eval start the SSH agent (#786 live checks) #1022, fix(cli): keepiq ssh-agent stays in the foreground under systemd #1144).Browser extension (separate artefact, still in progress)
Mobile apps (separate artefact, still in progress)
Security fixes
Other fixes and maintenance
Known red checks
These are pre-existing and not caused by this release:
build-and-load: Firefox 156 on the runner refuses the extension popup navigation in the load check.image: the image tag is built from the ref name, which is<n>/mergeon a pull request.AI disclosure: description drafted with Claude Code; reviewed by the author.
🤖 Generated with Claude Code