, backticks, `>` and `|` in ref names — only\nspaces and a short list of metacharacters are rejected. Two places in the DDEV\nsetup spliced a ref-derived value straight into an interpreter string:\n\n- `config.yaml` post-start hook interpolates `$BRANCH` into the comma\n[…]\nnch names (`main`,\n`fix/security-scan-findings`, `release/1.2.0`, `feature/TICKET-123-desc`,\n`chore/typo3-14.3`) pass through byte-identical.\n\nSigned-off-by: Sebastian Mendel \u003cinfo@sebastianmendel.de>", "is_bot": false, "headline": "fix(ddev): treat git ref names as untrusted in the local dev tooling", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-07-30T15:33:10Z", "body_truncated": true, "is_coding_agent": false }, { "oid": "354520fce765f16ebbae017209b71fcd27176a9b", "body": "…o Mode, token expiry, enumeration oracles) (#93)\n\nFixes the nine findings from a Claude Security scan of `58f02dc` (whole\nrepository, medium effort, three-verifier panel per finding). One commit\nper root cause; each was reproduced against the pre-fix code before the\nfix landed.\n\n## Findings\n\n| # | \n[…]\nwhere\nscheduling noise decides the last milliseconds.\n\nNote: Copilot could not review this PR (quota limit), so the\n`copilot_code_review` ruleset is satisfied by a review event containing\nno analysis.", "is_bot": false, "headline": "fix: address nine security-scan findings (switch-user escalation, Sud…", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-07-30T04:43:23Z", "body_truncated": true, "is_coding_agent": false }, { "oid": "e6ad29b7739503213ba9807ed8437f31367631e2", "body": "The floor is what proves the padding works — neither branch may answer before the\nbudget. The 2x ceiling added nothing to that and would fail on a runner where\nscheduling noise, not the controller, decides the last milliseconds. It is now a\n2-second sanity guard that still catches a grossly wrong sleep.\n\nSigned-off-by: Sebastian Mendel \u003cinfo@sebastianmendel.de>", "is_bot": false, "headline": "test: loosen the timing ceiling so a loaded runner cannot flake it", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-07-30T04:32:28Z", "body_truncated": false, "is_coding_agent": false }, { "oid": "02a33ec5baf12dd1067cc5252b7668947828c022", "body": "The switch-user guard and the login-token expiry check each added exit points to\nmethods that were already at the limit, so both exceeded the three-return\nthreshold (php:S1142).\n\nresolvePasskeyToken() now reads as its three decisions -- no token, rejected\ntoken, accepted token -- with cache access a\n[…]\negistrationOptionsAction() keeps its authorization guards and delegates option\ngeneration to buildRegistrationOptions(). No behaviour change.\n\nSigned-off-by: Sebastian Mendel \u003cinfo@sebastianmendel.de>", "is_bot": false, "headline": "refactor: extract the token-resolution and registration-option steps", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-07-30T04:22:25Z", "body_truncated": true, "is_coding_agent": false }, { "oid": "9e11f50ae60dc679e6a59a93bc3583a6d0b227c9", "body": "SonarCloud reads `require` on a value-returning config file as a bug\n(php:S2003), which pushed the PR's new-code reliability rating to 2 and failed\nthe quality gate. `require_once` cannot be swapped in blindly: it yields `true`\ninstead of the returned array on a second load. AjaxRoutesTest now inclu\n[…]\nross-branch coupling, and still fails on a one-sided delay.\nAlso uses assertNotSame() where an inverted assertTrue() was flagged (php:S5785).\n\nSigned-off-by: Sebastian Mendel \u003cinfo@sebastianmendel.de>", "is_bot": false, "headline": "test: include config files once per process and assert timing per branch", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-07-30T04:22:15Z", "body_truncated": true, "is_coding_agent": false }, { "oid": "624addedfa95ffefc6fc6bb514112771ca75b5e4", "body": "…imeException\n\nWebauthn\\Exception\\InvalidDataException extends \\Exception, so a structurally\ninvalid assertion object escaped the catch in verifyAndIssueToken(): an\nunauthenticated caller with a valid challenge token could turn the intended 401\nJSON into an uncaught-exception 500, which also skipped\n[…]\nneric AUTH_FAILED 401 with the\nbookkeeping intact; the exception class is logged so a genuine defect is still\ndiagnosable from the audit log.\n\nSigned-off-by: Sebastian Mendel \u003cinfo@sebastianmendel.de>", "is_bot": false, "headline": "fix(login): catch every assertion-verification failure, not just Runt…", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-07-30T04:15:01Z", "body_truncated": true, "is_coding_agent": false }, { "oid": "3f0d0bb3e87cc05b0afcec38d5bcf50ddfdd5e50", "body": "The unknown-username branch of /passkeys/login/options slept 50-150ms while the\nknown-username branch returned in a few milliseconds. Applying a delay to one\nbranch only does not normalize timing: it creates a large, reliably measurable\noracle. Two or three samples of the minimum round-trip separate\n[…]\ncumented that padding assumes the real work stays under budget, so per-IP rate\nlimiting remains the primary control against bulk enumeration.\n\nSigned-off-by: Sebastian Mendel \u003cinfo@sebastianmendel.de>", "is_bot": false, "headline": "fix(login): pad both login-options branches to one timing budget", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-07-30T04:12:45Z", "body_truncated": true, "is_coding_agent": false }, { "oid": "e3bfbd384404e7929bb4601c89f754b0ca681c62", "body": "The decoy allowCredentials meant to hide whether a username exists were\nrecognisable on sight: always exactly one descriptor, always a full 32-byte HMAC\nid (43 base64url chars, a length real authenticators rarely emit), always with\nempty transports. A single request to the public login-options endpo\n[…]\nuser pressing the passkey button gets a WebAuthn prompt that cannot succeed\nrather than an immediate rejection; password login is unaffected.\n\nSigned-off-by: Sebastian Mendel \u003cinfo@sebastianmendel.de>", "is_bot": false, "headline": "fix(login): make decoy credentials indistinguishable from real ones", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-07-30T04:10:04Z", "body_truncated": true, "is_coding_agent": false }, { "oid": "dfc23491282edc70649e02e771761452b238c7b1", "body": "The loginToken issued by /passkeys/login/verify is a bearer credential: the auth\nservice accepts it in userident and authenticates the mapped backend user without\nany WebAuthn ceremony. Its 120-second bound was delegated entirely to the cache\nTTL, so on a backend that ignores lifetimes an issued-but\n[…]\nmpt rather than lingering.\nTokens in the old bare-uid format are rejected, so a token issued just before an\nupgrade requires one fresh login.\n\nSigned-off-by: Sebastian Mendel \u003cinfo@sebastianmendel.de>", "is_bot": false, "headline": "fix(login): enforce login-token expiry in the token value", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-07-30T04:06:36Z", "body_truncated": true, "is_coding_agent": false }, { "oid": "9ae4db8cbf0afbc2c73d54747e77b24a6e662ed6", "body": "SimpleFileBackend discards the lifetime passed to set(), never checks expiry in\nget(), and its collectGarbage() is empty. The nonce cache holds challenge nonces\nand the single-use login tokens that authenticate a backend user, so with that\nbackend an issued-but-unredeemed token stayed valid indefini\n[…]\nnts\noverriding the backend (Redis, database) are unaffected: the assignment still\nuses ??=. Documentation updated to name the actual backend.\n\nSigned-off-by: Sebastian Mendel \u003cinfo@sebastianmendel.de>", "is_bot": false, "headline": "fix(cache): default the nonce cache to a TTL-honouring backend", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-07-30T04:03:50Z", "body_truncated": true, "is_coding_agent": false }, { "oid": "2a16e22dc3b0b58670f7361461681423f90f108c", "body": "The JS attached sudoModeInterceptor and README/Documentation stated that write\noperations require password re-verification, but no route carried the sudoMode\noption. RouteDispatcher::assertSudoMode() returns immediately in that case, so\nthe server never issued the 422 challenge the interceptor react\n[…]\ne's mfa route and the 15-minute grant the README documents. Read\nroutes stay ungated so a list request does not prompt on every panel render.\n\nSigned-off-by: Sebastian Mendel \u003cinfo@sebastianmendel.de>", "is_bot": false, "headline": "fix(routes): declare Sudo Mode on every passkey write route", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-07-30T04:00:35Z", "body_truncated": true, "is_coding_agent": false }, { "oid": "99371653c87e5b47d6c223b6849913a6919cf1ea", "body": "An admin impersonating another backend user could register a passkey on the\nimpersonated account: registrationVerifyAction() bound the credential to\n$GLOBALS['BE_USER']->user['uid'], which is the impersonated user in switch-user\nmode. Core's SwitchUserController only requires isAdmin(), so a non-mai\n[…]\nring core's MfaSetupController. The setup interstitial passes switch-user\nsessions through, since registration there would now be a dead end.\n\nSigned-off-by: Sebastian Mendel \u003cinfo@sebastianmendel.de>", "is_bot": false, "headline": "fix(management): refuse passkey writes in switch-user mode", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-07-30T03:57:26Z", "body_truncated": true, "is_coding_agent": false }, { "oid": "58f02dc4e7fd3791f2602648061f24e25903cdcc", "body": "…oint) (#92)\n\n## Summary\n\nConsolidates the duplicated passkey dashboard widgets into a single,\nunified Core widget set owned by `nr_passkeys_be`. Previously both\n`nr_passkeys_be` and `nr_passkeys_fe` registered a near-identical\nadoption + credentials widget pair into the same `nrpasskeys` group, so\n\n[…]\nis repo\n(`.github/workflows/ci.yml` `run-rector: false`, no `Build/rector.php`),\nso no Rector run applies.\n\n## Scope\n\nBackend only — `nr_passkeys_fe` and the demo are untouched and handled\nseparately.", "is_bot": false, "headline": "feat(dashboard): unify passkey dashboard widgets (backend extension p…", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-07-24T08:37:17Z", "body_truncated": true, "is_coding_agent": false }, { "oid": "ce400197ae7ea79298a1c6209001e9bd9824060c", "body": "Reduce SonarCloud new-code duplication below the 3% gate:\n\n- Extract the fake audience-stats provider mock into a shared\n AdoptionStatsProviderMockTrait, used by both data-provider tests instead\n of each carrying an identical private helper.\n- Drop the verbatim NullBackend cache-configuration bloc\n[…]\nce/rate-limit caches, so the ext_localconf.php file-backend\n defaults are sufficient.\n\nNo production code changes; behaviour is unchanged.\n\nSigned-off-by: Sebastian Mendel \u003cgithub@sebastianmendel.de>", "is_bot": false, "headline": "test: dedupe new dashboard-widget test arrangement", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-07-24T08:23:17Z", "body_truncated": true, "is_coding_agent": false }, { "oid": "8e693eada8af15ceb8b889f49138553e64e94b2f", "body": "Replace the backend-only dashboard data providers with a segmented,\ncross-extension design so nr_passkeys_be owns a single Core widget set\nthat nr_passkeys_fe contributes to, instead of each extension shipping a\nnear-identical widget pair (four widgets total when both are installed).\n\n- Add public e\n[…]\nacements of the old nrpasskeysbe-* widgets; acceptable for a 0.x minor.\n\nBumps the extension to 0.12.0 (ext_emconf, guides.xml, changelog).\n\nSigned-off-by: Sebastian Mendel \u003cgithub@sebastianmendel.de>", "is_bot": false, "headline": "feat(dashboard): consolidate passkey widgets into one unified widget set", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-07-24T08:12:33Z", "body_truncated": true, "is_coding_agent": false }, { "oid": "24abb27bf775d8187532c61ec2ede44d797ff3f4", "body": "Release v0.11.0 — new features since v0.10.3:\n- Conditional UI (autofill) for the backend passkey login (#88)\n- AJAX verify + single-use login token + WebAuthn Signal API (#90)\n\nVersion bumped in ext_emconf.php + Documentation/guides.xml.", "is_bot": false, "headline": "chore: release v0.11.0 (#91)", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-07-23T18:55:54Z", "body_truncated": false, "is_coding_agent": false }, { "oid": "12cbb58a2420a0c0cf6b30730d3f379926f0b8a3", "body": "Signed-off-by: Sebastian Mendel \u003cgithub@sebastianmendel.de>", "is_bot": false, "headline": "chore: release v0.11.0", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-07-23T18:50:34Z", "body_truncated": false, "is_coding_agent": false }, { "oid": "cd60a4fdcb70d35b3d037ce72ca6637ef3bd4c23", "body": "Replaces #89 (auto-closed when its stacked base branch #88 was\nmerged/deleted). Rebased onto main; same changes, no conditional-UI\ncommit (that landed via #88).\n\nThe backend passkey login now goes through `/passkeys/login/verify`,\nwhich runs the ceremony once and issues a **single-use login token** \n[…]\nerable/token/reason coverage + JS signal guard; PHPStan L10, CGL,\nSonarCloud, functional + E2E matrix.\n\nPart 3 of the passkey-UX hardening set (1: #88 merged, 2: FE\nnetresearch/t3x-nr-passkeys-fe#32).", "is_bot": false, "headline": "feat(login): AJAX verify + login token + WebAuthn Signal API (BE) (#90)", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-07-23T16:42:10Z", "body_truncated": true, "is_coding_agent": false }, { "oid": "7d42cb1062537c0dd1903429416cec5577ff83fd", "body": "- signalUnknownCredential: fire-and-forget via a void async IIFE (awaited call +\n local catch, no promise floating in a try) — clears the S4822 reliability bug.\n- Extract verifyAndIssueToken() shared by the discoverable and username-first\n verify paths: removes the duplicated verify/record/token block and drops\n verifyDiscoverable below the 3-return threshold.\n\nSigned-off-by: Sebastian Mendel \u003cgithub@sebastianmendel.de>", "is_bot": false, "headline": "refactor(login): clear remaining SonarCloud reliability + return-count", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-07-23T16:34:45Z", "body_truncated": false, "is_coding_agent": false }, { "oid": "aebd14bd4c85d513344d719081588d9e7c0a37b3", "body": "- signalUnknownCredential: use Promise.resolve().catch() (no floating promise /\n Promise-in-boolean) — clears the two reliability bugs that failed the gate.\n- LoginController: extract the duplicated 'Authentication failed' literal into a\n named constant.\n- resolvePasskeyToken / extractLoginToken: reduce early-return count.\n\nSigned-off-by: Sebastian Mendel \u003cgithub@sebastianmendel.de>", "is_bot": false, "headline": "refactor(login): fix SonarCloud reliability + smells on the verify flow", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-07-23T16:34:45Z", "body_truncated": false, "is_coding_agent": false }, { "oid": "3d8794cdccf1505b078865f25bc2750db1e2de78", "body": "Route the backend passkey login through the /passkeys/login/verify endpoint\ninstead of re-verifying the raw assertion in the auth service. The endpoint runs\nthe WebAuthn ceremony once and, on success, issues a single-use login token\n(120s TTL) that the JS submits through the standard login form; the\n[…]\nerify token + discoverable unknown-credential reason + auth token\npath), JS (signal guard). Full unit suite green, CGL + PHPStan L10 clean.\n\nSigned-off-by: Sebastian Mendel \u003cgithub@sebastianmendel.de>", "is_bot": false, "headline": "feat(login): AJAX verify + login token + WebAuthn Signal API", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-07-23T16:34:45Z", "body_truncated": true, "is_coding_agent": false }, { "oid": "cfee45783bb6344ad53b0f285027d7c40986ebbd", "body": "…(#88)\n\nSurfaces discoverable passkeys directly in the standard backend login\nform's **username-field autofill** (Conditional UI / `mediation:\n'conditional'`), so returning users don't have to click the passkey\nbutton. Ports the pattern the frontend extension already uses.\n\n**Behaviour**\n- Feature-d\n[…]\nverage for the\nautocomplete-merge logic); the full ceremony is validated like the\nbutton flow (E2E / virtual authenticator).\n\nPart 1 of a passkey-UX hardening set (2: FE Signal API, 3: BE Signal\nAPI).", "is_bot": false, "headline": "feat(login): conditional UI (autofill) for the backend passkey login …", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-07-23T16:33:47Z", "body_truncated": true, "is_coding_agent": false }, { "oid": "d7585eaaa09bd6050aafe0707c4158c91ef80c37", "body": "Surface discoverable passkeys directly in the standard TYPO3 backend login\nform's username-field autofill, so a returning user no longer has to click the\n\"Sign in with a passkey\" button. On init, when discoverable login is enabled and\nthe browser supports conditional mediation, the field gets\n`autoc\n[…]\nan explicit button click\n aborts the pending conditional ceremony first.\n- autocomplete token is merged, not clobbered, and de-duplicated.\n\nSigned-off-by: Sebastian Mendel \u003cgithub@sebastianmendel.de>", "is_bot": false, "headline": "feat(login): conditional UI (autofill) for the backend passkey login", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-07-23T13:27:08Z", "body_truncated": true, "is_coding_agent": false }, { "oid": "ab8fe632d76c723bd47fa2a13fc4658ebf678170", "body": "## What was broken\n\n`Documentation/guides.xml` had a **corrupted XML declaration** — the\nversion attribute had been rewritten from `1.0` to `0.10.2` (`\u003c?xml\nversion=\"0.10.2\" encoding=\"UTF-8\"?>`), which is not a valid XML version.\nThe `\u003cproject>` `version`/`release` attributes were also stale\n(`0.10.\n[…]\n--schema guides.xsd Documentation/guides.xml` —\nschema-valid ✅\n- `render-guides:latest configure Documentation` — exit 0 ✅\n- `render-guides:latest render …` — exit 0 (18 documents) ✅\n\nRelease v0.10.3.", "is_bot": false, "headline": "fix(docs): repair guides.xml and add docs render CI (#87)", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-07-22T05:55:29Z", "body_truncated": true, "is_coding_agent": false }, { "oid": "1d25eee79038e470c97ab6a5d555b584ea485df7", "body": "The XML declaration had been corrupted to version=\"0.10.2\" (invalid; must be \"1.0\") and the \u003cproject> version/release were stale (0.10.1), which made the TYPO3 guides render fatal at the strict 'configure' step. Adds the docs render job so CI catches guides.xml errors. Release v0.10.3.\n\nSigned-off-by: Sebastian Mendel \u003cgithub@sebastianmendel.de>", "is_bot": false, "headline": "fix(docs): repair guides.xml and add docs render CI", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-07-21T15:45:40Z", "body_truncated": false, "is_coding_agent": false }, { "oid": "5384ce7f1febe404a58a5787e0314912a876343b", "body": "Patch release: v14 3-color icon rework + unified palette, dark-mode\nfixes for interstitial/banner/login divider\n([#84](https://github.com/netresearch/t3x-nr-passkeys-be/pull/84)), and\ntwo admin-only dashboard widgets nrpasskeysbe-adoption +\nnrpasskeysbe-credentials\n([#85](https://github.com/netresearch/t3x-nr-passkeys-be/pull/85)).\nAdditive/non-breaking 0.x patch by policy.", "is_bot": false, "headline": "chore: release v0.10.2 (#86)", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-07-21T15:05:43Z", "body_truncated": false, "is_coding_agent": false }, { "oid": "dd2cac9eb1eebb5e9e01cd34007594a107e0f8c6", "body": "Signed-off-by: Sebastian Mendel \u003cgithub@sebastianmendel.de>", "is_bot": false, "headline": "chore: release v0.10.2", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-07-21T14:41:11Z", "body_truncated": false, "is_coding_agent": false }, { "oid": "7845b2eddcda9adec7b149a45c3e28448fa5fa4a", "body": "## Problem\n\n1. **Code examples unreadable in dark mode** (reported): on\n`/typo3/module/admin/vault` (TYPO3 14.3), the code examples in the\nOverview/Help templates rendered in the core magenta inline-code color.\nRoot cause: TYPO3 v14 forces `code { color: var(--typo3-text-color-code)\n!important }` on\n[…]\nsabled users are not counted (gemini-code-assist\nfinding). Includes new functional tests (real DB) for the join and for\nthe aggregate/getStats() consistency. CI re-ran fully green on the\nupdated head.", "is_bot": false, "headline": "feat(dashboard): add passkey adoption and credential-count widgets (#85)", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-07-21T14:39:25Z", "body_truncated": true, "is_coding_agent": false }, { "oid": "14cb31a6086b59effc0a292293a019d07fb419a3", "body": "… surfaces (#84)\n\n## Problem\n\n**Icons** — the four SVGs mixed three drifted teals\n(`#2999a4`/`#2d99a4`/`#2F99A4`) and inconsistent styles:\n`ModuleIcon.svg` mixed hardcoded teal paths with `currentColor`,\n`Extension.svg` was the generic Netresearch corporate logo with a\nfragile embedded `\u003cstyle>` blo\n[…]\ns://docs.typo3.org/permalink/t3coreapi:icon-registration),\n[color scheme user\nsetting](https://docs.typo3.org/c/typo3/cms-core/main/en-us/Changelog/13.3/Feature-104607-IntroduceColorSchemeSwitch.html)", "is_bot": false, "headline": "feat: v14 three-color icons and light/dark scheme support for backend…", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-07-21T14:39:18Z", "body_truncated": true, "is_coding_agent": false }, { "oid": "8e543f2f00b8c40511c5bb1e77885d07de0ac5a0", "body": "Join be_users in countActiveCredentials() so leftover active credentials\nof soft-deleted or disabled backend users no longer inflate the\n\"Active passkeys\" widget number. Adds a functional test proving the\njoin against a real database and pins the aggregate counts to the\ngetStats() results.\n\nThe pre-\n[…]\ngning it would change shipped admin-module numbers and is out of\nscope for this feature PR.\n\nAddresses gemini-code-assist review on PR #85.\n\nSigned-off-by: Sebastian Mendel \u003cgithub@sebastianmendel.de>", "is_bot": false, "headline": "fix(dashboard): count only credentials of active backend users", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-07-21T12:30:00Z", "body_truncated": true, "is_coding_agent": false }, { "oid": "a6d04a6e0fadf0e51c10272d678851200b880c74", "body": "Register two admin-only TYPO3 dashboard widgets in a new \"Passkeys\"\nwidget group (nrpasskeys):\n\n- nrpasskeysbe-adoption: core DoughnutChartWidget showing active backend\n users with vs. without passkeys\n- nrpasskeysbe-credentials: core NumberWithIconWidget showing the total\n of active (not revoked)\n[…]\nre excluded from\nPHPStan analysis because the missing interface causes an unignorable\ninternal reflection error on the v12/v13 matrix legs.\n\nSigned-off-by: Sebastian Mendel \u003cgithub@sebastianmendel.de>", "is_bot": false, "headline": "feat(dashboard): add passkey adoption and credential-count widgets", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-07-21T12:22:41Z", "body_truncated": true, "is_coding_agent": false }, { "oid": "6483bbbe42ae7581f20a479893656afbdae76dd0", "body": "Extract a renderInterstitialBody() helper for the two color-scheme\ntests so the shared Required-enforcement boilerplate is not duplicated\n(SonarCloud new-code duplication gate).\n\nSigned-off-by: Sebastian Mendel \u003cgithub@sebastianmendel.de>", "is_bot": false, "headline": "refactor(tests): deduplicate interstitial scheme test setup", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-07-21T11:34:58Z", "body_truncated": false, "is_coding_agent": false }, { "oid": "87a297c887a32d098b2fd102cbe3a11b3e75f72f", "body": "Three surfaces hardcoded palette colors and broke in one of the TYPO3\nv14 color schemes:\n\n- Setup interstitial: was permanently dark (#1e1e1e page with an\n off-brand #0078d4 button) regardless of scheme. The page now declares\n color-scheme: light dark with a light default palette, dark values\n vi\n[…]\nles to classes in the new\nResources/Public/Css/backend.css, loaded by both event listeners\n(CSP-friendly, no functional change on v12/v13).\n\nSigned-off-by: Sebastian Mendel \u003cgithub@sebastianmendel.de>", "is_bot": false, "headline": "fix(ui): make backend surfaces adapt to the v14 light/dark schemes", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-07-21T11:29:25Z", "body_truncated": true, "is_coding_agent": false }, { "oid": "2b4c25cf28b096f05581a9a7d4842e9afe23045d", "body": "Redraw the four extension icons as a consistent person+key motif and\nunify the three drifted teals (#2999a4/#2d99a4/#2F99A4) to the single\nbrand value #2F99A4:\n\n- ModuleIcon.svg: v14 three-color spec - currentColor primary glyph,\n 40%-opacity secondary detail, single brand-teal accent on the key vi\n[…]\n\n\nThe v14/legacy runtime switch in Configuration/Icons.php and both icon\nidentifiers (passkeys-be-login, passkeys-be-module) are unchanged.\n\nSigned-off-by: Sebastian Mendel \u003cgithub@sebastianmendel.de>", "is_bot": false, "headline": "feat(icons): redraw all icons to the TYPO3 v14 three-color icon style", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-07-21T11:29:11Z", "body_truncated": true, "is_coding_agent": false }, { "oid": "ae9e4818fdadca1f233927137c32021900da23a9", "body": "Patch release shipping\n[#82](https://github.com/netresearch/t3x-nr-passkeys-be/pull/82): the\npasskey audit log moves from the web-reachable, often-unwritable\n`typo3temp/var/log/` to `Environment::getVarPath()/log/` — an unwritable\nFileWriter throws #1321804422 on every warning-logging request (took the\ntypo3-demo backend down). Also catches `Documentation/guides.xml` up\nfrom 0.9.4 (missed in the v0.10.0 bump).", "is_bot": false, "headline": "chore: release v0.10.1 (#83)", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-07-21T11:16:06Z", "body_truncated": false, "is_coding_agent": false }, { "oid": "f26fc06f03146d9ecf4e0d8540497c8b8cef712f", "body": "Signed-off-by: Sebastian Mendel \u003cgithub@sebastianmendel.de>", "is_bot": false, "headline": "chore: release v0.10.1", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-07-21T11:08:46Z", "body_truncated": false, "is_coding_agent": false }, { "oid": "40c792a6828e3d2be341bec80c2e59925b6180bb", "body": "## Summary\n\nThe audit-log FileWriter used the relative path\n`typo3temp/var/log/passkey_auth.log`, which resolves **below the public\nweb root**. Two problems:\n\n1. **Availability**: in containerized deployments the directory is often\nnot writable by the PHP user, or the file gets created root-owned by\n[…]\nr ci:test:php:cgl` ✔, `ci:test:php:phpstan` ✔ (level 10),\n`ci:test:php:unit` ✔ (598 tests).\n- After release + demo redeploy: `/typo3/` serves 200 again and warnings\nland in `var/log/passkey_auth.log`.", "is_bot": false, "headline": "fix(log): write passkey audit log to the project var path (#82)", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-07-21T11:08:32Z", "body_truncated": true, "is_coding_agent": false }, { "oid": "0ec5f7fbd2c085eb7480733ea7d7222ac1552a2d", "body": "The FileWriter was configured with the relative path\ntypo3temp/var/log/passkey_auth.log, which resolves below the public web\nroot. In containerized deployments that directory is frequently not\nwritable by the PHP user (or the file gets created root-owned by CLI\nruns), and an unwritable FileWriter th\n[…]\nr a security audit log.\n\nUse Environment::getVarPath() . '/log/passkey_auth.log' instead, the\ncanonical writable location in composer mode.\n\nSigned-off-by: Sebastian Mendel \u003cgithub@sebastianmendel.de>", "is_bot": false, "headline": "fix(log): write passkey audit log to the project var path", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-07-21T11:04:07Z", "body_truncated": true, "is_coding_agent": false }, { "oid": "4243e511e4b87c1aaeb37ad143972cd74462c479", "body": "## Release v0.10.0\n\nMinor bump: 54 commits since v0.9.4, including three new features\nalongside a security-hardening cluster.\n\n### Features\n- i18n: admin Help page fully translatable\n- i18n: dashboard onboarding infoboxes translatable\n- CLI: `passkeys:recovery` command for out-of-band recovery\n\n### \n[…]\nTooling\n- Adopted the canonical TYPO3-extension CI template; PHPStan raised to\nlevel 10\n\nBumps `ext_emconf.php` to 0.10.0. Tag `v0.10.0` will be pushed after\nmerge to trigger the TER + GitHub release.", "is_bot": false, "headline": "chore(release): v0.10.0 (#81)", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-06-25T13:00:41Z", "body_truncated": true, "is_coding_agent": false }, { "oid": "33e2ffc59c7e5a696ff69131cc031c546c0f4505", "body": "Signed-off-by: Sebastian Mendel \u003cgithub@sebastianmendel.de>", "is_bot": false, "headline": "chore(release): v0.10.0", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-06-25T12:54:04Z", "body_truncated": false, "is_coding_agent": false }, { "oid": "675b717f6359bd3ee86596eef908ae6227638503", "body": "…ume (#80)\n\n## Summary\n\nThe rate-limit gate split the check and the record across two separate\nlock acquisitions: `checkRateLimit()` (read + compare, release) then\nlater `recordAttempt()` (increment, release). On the live login path\nthat leaves a **check-then-record window** — N concurrent requests \n[…]\nmic increment under the\nlimit, reject at the limit without incrementing, fail-closed when the\nlock can't be acquired.\n- Local `make test-unit`: **573 passed**; `phpstan` (1G): no errors;\n`cgl`: clean.", "is_bot": false, "headline": "fix(ratelimit): close rate-limit check/record TOCTOU with atomic cons…", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-06-25T11:52:32Z", "body_truncated": true, "is_coding_agent": false }, { "oid": "751f14acd5f00c8892626a9875231faa5f45fa5d", "body": "…#78)\n\n## Summary\n\nThe system-maintainer privilege boundary — in TYPO3 only system\nmaintainers may manage other admins/maintainers — was enforced **only in\nthe FormEngine UI element** (`PasskeyInfoElement`), which hides the\nrevoke/unlock buttons. The admin AJAX endpoints enforced just\n`isAdmin()`:\n\n\n[…]\nmaintainer → maintainer) on `removeAction`\nand `listAction`, and allowed (maintainer → maintainer) on\n`removeAction`.\n- Local `make test-unit`: **573 passed**; `phpstan` (1G): no errors;\n`cgl`: clean.", "is_bot": false, "headline": "fix(admin): enforce system-maintainer boundary on passkey admin API (…", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-06-25T11:52:04Z", "body_truncated": true, "is_coding_agent": false }, { "oid": "12c41ffa3d2400d1c4f1ff7493237b919bdff7ea", "body": "… rpId/origin (#79)\n\nTwo backend hardening fixes from a security review, as **two separable\ncommits**.\n\n## 1. Narrow the interstitial AJAX exemption (`bad9b9e`)\n`PasskeySetupInterstitial` exempted any route starting with `ajax_`.\nTYPO3 registers every backend AJAX route as `ajax_\u003cid>`, so the bare\np\n[…]\nr\nsoftened to a logged warning — the first commit stands alone.\n\n## Test plan\n- New/updated unit tests in both areas.\n- Local `make test-unit`: **573 passed**; `phpstan` (1G): no errors;\n`cgl`: clean.", "is_bot": false, "headline": "fix: narrow interstitial AJAX exemption + fail closed on Host-derived…", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-06-25T11:51:26Z", "body_truncated": true, "is_coding_agent": false }, { "oid": "be7400faff23098952ae502ceb17c749a9e825bd", "body": "… path (#77)\n\n## Summary\n\n`discoverableLoginEnabled` was enforced only at challenge issuance\n(`LoginController::optionsAction`), not on the auth-service path that\nestablishes the session. Because a challenge token carries no mode\nbinding, a username-first token could otherwise drive the discoverable\n[…]\n lockout DoS via `recordFailure(countUserLockout: false)`. This PR is\nnow scoped to the discoverable-flag enforcement only.\n\n## Validation\nPHPStan level 10, full unit suite, and CGL all green locally.", "is_bot": false, "headline": "fix(auth): enforce discoverableLoginEnabled on the auth-service login…", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-06-25T11:51:22Z", "body_truncated": true, "is_coding_agent": false }, { "oid": "ec3a68d52d39fe42abad63969fb10ef0a0e18a72", "body": "Review follow-up:\n- Rename optionsActionRecordsAttempt/verifyActionRecordsAttempt (they now assert\n consumeRateLimit, not the removed recordAttempt) to ...ConsumesRateLimit.\n- Add a boundary test that consumeRateLimit allows and increments the last\n attempt at count 4 -> 5 (guards the >= vs > comp\n[…]\nontroller that consumeRateLimit counts the attempt up front,\n so a subsequently lockout-rejected request still consumes rate-limit budget.\n\nSigned-off-by: Sebastian Mendel \u003cgithub@sebastianmendel.de>", "is_bot": false, "headline": "test(ratelimit): clarify consume-rate-limit tests and ordering", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-06-25T11:46:36Z", "body_truncated": true, "is_coding_agent": false }, { "oid": "d3ba5d6c6bdded4692a482a6b1a153d8f983ac41", "body": "checkRateLimit() and recordAttempt() acquire separate locks, so the live login\npath (checkRateLimit -> ... -> recordAttempt) left a check-then-record window:\nN concurrent requests could all pass the check before any of them incremented,\novershooting the configured rate limit by the in-flight count. \n[…]\n unit tests for consumeRateLimit (atomic increment under limit, reject at\nlimit without incrementing, fail-closed on lock-acquire failure).\n\nSigned-off-by: Sebastian Mendel \u003cgithub@sebastianmendel.de>", "is_bot": false, "headline": "fix(ratelimit): close rate-limit check/record TOCTOU with atomic consume", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-06-25T11:46:36Z", "body_truncated": true, "is_coding_agent": false }, { "oid": "cea15b138a113039816abebebec777f1781c3fdc", "body": "…esent\n\nAddresses Copilot review: assertHostTrustEnforced() previously fired in getEffectiveRpId/getEffectiveOrigin even on the CLI/cron path where the Host header is empty and the 'localhost' fallback is a safe anchor, throwing spuriously. Now the trust check runs only when a non-empty request Host would actually become the WebAuthn anchor.\n\nSigned-off-by: Sebastian Mendel \u003cgithub@sebastianmendel.de>", "is_bot": false, "headline": "fix(config): only enforce host-trust when a request Host header is pr…", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-06-25T11:44:04Z", "body_truncated": false, "is_coding_agent": false }, { "oid": "4feefc6c2a0efde34e988260391b293b252a63bf", "body": "Review follow-ups:\n- Correct the assertHostTrustEnforced() docblock/comment: an empty\n trustedHostsPattern is treated by TYPO3 core as invalid and rejects every\n Host (fail-closed), not \"accept any Host\"; only '.*' is allow-all. The guard\n still refuses to derive an anchor from either, now accura\n[…]\nut/\n ajax_mfa exemptions, so the narrowed allowlist is regression-protected for the\n auth/logout/MFA routes an enforced user still needs.\n\nSigned-off-by: Sebastian Mendel \u003cgithub@sebastianmendel.de>", "is_bot": false, "headline": "test+docs: review follow-ups for host-binding and interstitial scope", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-06-25T11:41:15Z", "body_truncated": true, "is_coding_agent": false }, { "oid": "b7a23b6c95a5ccead4af1aaac1320e7c139cda5d", "body": "…Host\n\nWhen rpId/origin are left empty they are derived from the request Host header.\nThat value is only trustworthy when TYPO3's host-header validation is enforcing\na pattern; the framework treats both '' and the allow-all '.*' trustedHostsPattern\nas \"accept any Host\", which makes the derived WebAu\n[…]\nostsPattern or pins rpId/origin (the documented production\nrequirement). Pinning rpId/origin or tightening trustedHostsPattern resolves it.\n\nSigned-off-by: Sebastian Mendel \u003cgithub@sebastianmendel.de>", "is_bot": false, "headline": "fix(config): fail closed when deriving rpId/origin from an untrusted …", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-06-25T11:40:37Z", "body_truncated": true, "is_coding_agent": false }, { "oid": "dabdde68236c85b2a590690293376248bfd5ce70", "body": "The passkey-setup interstitial exempted any route whose identifier starts with\n'ajax_'. TYPO3 core registers every backend AJAX route as 'ajax_'.\u003cid>, so the\nbare prefix exempted ~260 state-changing endpoints (ajax_record_process =\nDataHandler save, ajax_file_process, ...). An enforced-but-unenrolle\n[…]\n\nAdds a regression test asserting ajax_record_process is blocked, and repoints\nthe existing AJAX-exemption tests to real enrollment routes.\n\nSigned-off-by: Sebastian Mendel \u003cgithub@sebastianmendel.de>", "is_bot": false, "headline": "fix(middleware): narrow interstitial AJAX exemption to enrollment routes", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-06-25T11:40:37Z", "body_truncated": true, "is_coding_agent": false }, { "oid": "4632b5c234b7b43f4577f883397ad1a77b9286b2", "body": "… path\n\ndiscoverableLoginEnabled was checked only at challenge issuance\n(LoginController::optionsAction), not on the auth-service path that establishes\nthe session. Because a challenge token carries no mode binding, a username-first\ntoken could otherwise drive the discoverable code path even when an\n[…]\ning change originally in this PR was dropped: main's ca2180a\nalready addresses the lockout DoS via recordFailure(countUserLockout: false).)\n\nSigned-off-by: Sebastian Mendel \u003cgithub@sebastianmendel.de>", "is_bot": false, "headline": "fix(auth): enforce discoverableLoginEnabled on the auth-service login…", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-06-25T11:37:18Z", "body_truncated": true, "is_coding_agent": false }, { "oid": "17cdc931800b4df49bebefcfed64498b4ac6bafe", "body": "Addresses Copilot review (avoid the '\\intval' string callable) while keeping PHPStan level 10 happy: iterate and is_numeric-narrow each maintainer ID before the (int) cast, instead of array_map over a mixed-typed array (which level 10 rejects).\n\nSigned-off-by: Sebastian Mendel \u003cgithub@sebastianmendel.de>", "is_bot": false, "headline": "fix(admin): normalize system-maintainer IDs without a string callable", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-06-25T11:28:56Z", "body_truncated": false, "is_coding_agent": false }, { "oid": "6cb5f001d435464dc254628d2042279ce9dae9d6", "body": "- Extract the repeated 'Insufficient privileges to manage this user' literal\n into ERROR_INSUFFICIENT_PRIVILEGES (used by all six guards).\n- Extract setUpNonMaintainerAdminTargetingMaintainer() and assertManagementDenied()\n test helpers to remove the duplicated setup/assert blocks across the\n maintainer-boundary tests.\n\nReduces new-code duplication below the SonarCloud quality gate; behaviour\nunchanged.\n\nSigned-off-by: Sebastian Mendel \u003cgithub@sebastianmendel.de>", "is_bot": false, "headline": "refactor(admin): dedupe maintainer-guard literal and tests (SonarCloud)", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-06-25T11:23:14Z", "body_truncated": false, "is_coding_agent": false }, { "oid": "4810d2939c89410c02275f02cead9c508ee84465", "body": "…dpoints\n\nReview follow-up: sendReminderAction and clearNudgeAction also act on a target\nbackend user by beUserUid (they write be_users.passkey_nudge_until), but were\nnot given the isManagementAllowedFor() guard the other four admin actions\nreceived, leaving the system-maintainer boundary inconsiste\n[…]\nr/clear-nudge plus a test that a non-maintainer\nadmin can still manage a non-maintainer target when a systemMaintainers list is\nconfigured.\n\nSigned-off-by: Sebastian Mendel \u003cgithub@sebastianmendel.de>", "is_bot": false, "headline": "fix(admin): extend system-maintainer guard to reminder/clear-nudge en…", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-06-25T11:23:14Z", "body_truncated": true, "is_coding_agent": false }, { "oid": "cb36b21eb1d03104596ae2970df33bd7510f1b1e", "body": "The system-maintainer privilege boundary (only system maintainers may manage\nother maintainers) was enforced only in the FormEngine UI element, which hides\nbuttons. The admin AJAX endpoints (list/remove/unlock/revoke-all) gated solely\non isAdmin(), so any backend admin could call them directly again\n[…]\ntainer targets are unaffected.\n\nAdds unit tests for the denied (non-maintainer to maintainer) and allowed\n(maintainer to maintainer) paths.\n\nSigned-off-by: Sebastian Mendel \u003cgithub@sebastianmendel.de>", "is_bot": false, "headline": "fix(admin): enforce system-maintainer boundary on passkey admin API", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-06-25T11:23:14Z", "body_truncated": true, "is_coding_agent": false }, { "oid": "ee41bd236a614765daa956e697d0dc41f1e26520", "body": "Migrate CI to the canonical `typo3-extension` template\n(netresearch/.github): explicit per-call-site permissions on every\nreusable, drift-enforced. Security/quality jobs (checks.yml) are\nbyte-governed; the test matrix (ci.yml) and release.yml are per-repo\n(intentional-drift, preserved from this repo).", "is_bot": false, "headline": "ci: adopt canonical typo3-extension template (#71)", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-06-17T13:21:59Z", "body_truncated": false, "is_coding_agent": false }, { "oid": "3b059a400c1402b41fc2c2046067baaada1e4b57", "body": "Opengrep --config auto runs python.django.security.django-no-csrf-token\nagainst all files and flags Resources/Private/Templates/Interstitial/Setup.html:34\n(\u003cform method=\"post\">). That file is a TYPO3 Fluid (.html) template, not a\nDjango template, so the rule does not apply.\n\nAdd a scoped .semgrepign\n[…]\n which invokes 'opengrep scan ... .' with a\npath arg) excluding only that single template path. Opengrep continues to\nscan all other files.\n\nSigned-off-by: Sebastian Mendel \u003cgithub@sebastianmendel.de>", "is_bot": false, "headline": "ci: ignore Django CSRF false-positive on Fluid template", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-06-17T12:55:46Z", "body_truncated": true, "is_coding_agent": false }, { "oid": "fa3c3bc2d5c4fe35593bbc8f52f026fd5b3616ce", "body": "…odule (TEST-1) (#76)\n\nTEST-1 from the review (tracked in #73). The JS unit tests exercised\nre-implemented **copies** of the base64 helpers, not the shipped code.\n\n## Change\n\n- **`Util/Base64.js`** — single shared module with `base64urlToBuffer` /\n`bufferToBase64url` / `bufferToBase64`, imported by \n[…]\nss against the real\n`Util/Base64.js`, PHPStan L10 + CGL clean, `InjectPasskeyLoginFields`\nunit test green. CI runs e2e (full login ceremony) + SonarCloud\n(maintainability gate).\n\nCloses TEST-1 in #73.", "is_bot": false, "headline": "refactor(js): share WebAuthn base64 helpers + modernize login to ES m…", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-06-16T20:09:45Z", "body_truncated": true, "is_coding_agent": false }, { "oid": "643e6a68f3e8a45c80c1dd19dac3662f9f9a4121", "body": "… module (TEST-1)\n\nThe JS unit tests exercised re-implemented copies of the base64 helpers, not the\nshipped code. Extract a single Util/Base64.js imported by both passkey modules and by\nthe tests, so the tests now cover the real implementation.\n\nPasskeyManagement.js (already an ES module) imports th\n[…]\n 63 JS tests pass against the real Util/Base64.js; PHPStan L10, CGL, and\nthe InjectPasskeyLoginFields unit test green.\n\nCloses TEST-1 in #73.\n\nSigned-off-by: Sebastian Mendel \u003cinfo@sebastianmendel.de>", "is_bot": false, "headline": "refactor(js): share WebAuthn base64 helpers, modernize login to an ES…", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-06-16T20:00:51Z", "body_truncated": true, "is_coding_agent": false }, { "oid": "2927a63a2549dd9a744c60c9a6c4a6bea2ac6104", "body": "… ceremonies (ARCH-2) (#75)\n\nARCH-2 from the review (tracked in #73). The ~560-line `WebAuthnService`\nmixed the attestation (registration) and assertion (authentication)\nceremonies plus the shared WebAuthn library plumbing in one class.\n\n## Change\n\nExtracts three focused collaborators **behind the u\n[…]\nlive TYPO3 v13.4 backend (login page\nreturns 200)\n- CI runs the functional matrix + e2e, which exercise the\n`makeInstance(WebAuthnService)` auth path and the full login ceremony\n\nCloses ARCH-2 in #73.", "is_bot": false, "headline": "refactor(service): split WebAuthnService into attestation + assertion…", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-06-16T19:21:18Z", "body_truncated": true, "is_coding_agent": false }, { "oid": "070da9665e261afbc939fb7d00de130b169832a3", "body": "…creenshots (#74)\n\nLive-backend follow-ups from the review (tracked in #73). All changes\nverified on a running TYPO3 v13.4 DDEV backend.\n\n## Changes\n\n- **Fix double-encoded ampersand** — the dashboard \"Passkeys & MFA\"\ninfobox passed `&` as the `f:be.infobox` *title* argument; the\nViewHelper re-e\n[…]\nage —\nheadings, MFA table, FAQ, links — all confirmed). Screenshots captured\nfrom the same backend. PHP/JS/PHPStan/CGL unaffected (template + XLF +\nimage changes only).\n\nPart of the #73 follow-up set.", "is_bot": false, "headline": "fix(admin): dashboard infobox rendering + i18n + real documentation s…", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-06-16T19:14:08Z", "body_truncated": true, "is_coding_agent": false }, { "oid": "5a96ed7d324fcd7b53d0aa63ed5af641ce2eb982", "body": "… ceremonies\n\nThe ~560-line WebAuthnService mixed the attestation (registration) and assertion\n(authentication) ceremonies plus the shared WebAuthn library plumbing in one class.\nExtract three focused collaborators behind the unchanged public surface:\n\n- WebAuthnCeremonyFactory: shared serializer (c\n[…]\nss, PHPStan level 10 + phpat architecture rules clean, CGL\nclean, and the DI container compiles on a live TYPO3 v13 backend (login page 200).\n\nSigned-off-by: Sebastian Mendel \u003cinfo@sebastianmendel.de>", "is_bot": false, "headline": "refactor(service): split WebAuthnService into attestation + assertion…", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-06-16T19:11:19Z", "body_truncated": true, "is_coding_agent": false }, { "oid": "32840c8b1d5997bedf3cfa06693ea4f72ad464aa", "body": "Help.html (rollout guide, recovery procedures, MFA coexistence table, FAQ) was 414\nlines of hardcoded English. Extract all prose into 80 locallang.xlf trans-units via\nf:translate, keeping inline markup (\u003ccode>/\u003cem>/\u003cstrong>/\u003ca>) in the XLF source rendered\nthrough f:format.raw, and passing the dynami\n[…]\nline code/emphasis render,\nthe three Dashboard links resolve to real URLs, and the page is visually identical to\nthe previous English output.\n\nSigned-off-by: Sebastian Mendel \u003cinfo@sebastianmendel.de>", "is_bot": false, "headline": "feat(i18n): make the admin Help page fully translatable", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-06-16T18:58:55Z", "body_truncated": true, "is_coding_agent": false }, { "oid": "11dbc50d3c83ebcbaedfac4fb7f6bbd8e92bd794", "body": "The Getting started, Configuration note, and Passkeys & MFA infoboxes on the admin\ndashboard were hardcoded English. Extract their text into locallang.xlf via f:translate,\nkeeping inline markup (\u003cstrong>/\u003cem>/\u003ccode>/\u003ca>) in the template and passing the\nauto-detected rpId as a %s argument. Verified on a live TYPO3 v13 backend: all three\ninfoboxes render identically with keys resolved, markup intact, and no raw label leakage.\n\nSigned-off-by: Sebastian Mendel \u003cinfo@sebastianmendel.de>", "is_bot": false, "headline": "feat(i18n): make dashboard onboarding infoboxes translatable", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-06-16T18:14:15Z", "body_truncated": false, "is_coding_agent": false } ], "releases_count": 22, "commits_last_year": 452, "latest_release_at": "2026-07-24T08:40:15Z", "latest_release_tag": "v0.12.0", "releases_from_tags": false, "days_since_last_push": 0, "active_weeks_last_year": 19, "days_since_latest_release": 12, "mean_days_between_releases": 6.3 }, "artifacts": { "collected": true, "structure": [ "tree.compose", "tree.dockerfile" ], "declarations": [ { "name": "netresearch/nr-passkeys-be", "path": "composer.json", "tokens": [ "composer.type:typo3-cms-extension" ], "ecosystem": "packagist" }, { "name": null, "path": "package.json", "tokens": [ "npm.private" ], "ecosystem": "npm" } ] }, "community": { "has_readme": true, "has_license": true, "readme_badges": { "hosts": [ "api.securityscorecards.dev", "codecov.io", "github.com", "shields.io", "www.bestpractices.dev" ], "total": 10, "header": 4, "collected": true, "has_inspect_badge": false }, "has_description": true, "has_contributing": true, "health_percentage": 100, "has_issue_template": false, "has_code_of_conduct": true, "has_pull_request_template": true }, "ecosystem": { "packages": [ { "name": "netresearch/nr-passkeys-be", "exists": true, "license": "GPL-2.0-or-later", "keywords": [ "Authentication", "backend", "typo3", "Passwordless", "FIDO2", "webauthn", "passkeys" ], "ecosystem": "packagist", "categories": [], "matches_repo": true, "registry_url": "https://packagist.org/packages/netresearch/nr-passkeys-be", "declared_type": "typo3-cms-extension", "is_deprecated": false, "latest_version": "v0.12.0", "repository_url": "https://github.com/netresearch/t3x-nr-passkeys-be", "versions_count": 23, "total_downloads": 5516, "dependents_count": 1, "deprecation_note": null, "maintainers_count": null, "monthly_downloads": 2161, "first_published_at": null, "latest_published_at": "2026-07-24T08:37:17Z", "latest_version_yanked": null, "days_since_latest_publish": 12 } ] }, "popularity": { "forks": 0, "stars": 3, "watchers": 1, "fork_history": { "days": [], "complete": true, "collected": 0, "total_forks": 0 }, "star_history": null, "open_issues_and_prs": 1 }, "ai_readiness": { "has_nix": false, "example_dirs": [], "has_llms_txt": false, "has_dockerfile": true, "has_mcp_signal": false, "bootstrap_files": [ "Makefile" ], "api_schema_files": [], "has_devcontainer": false, "typecheck_configs": [], "toolchain_manifests": [], "largest_source_bytes": 66621, "source_files_sampled": 144, "oversized_source_files": 1, "agent_instruction_files": [ ".ddev/AGENTS.md", ".github/copilot-instructions.md", ".github/workflows/AGENTS.md", ".github/workflows/CLAUDE.md", "AGENTS.md", "CLAUDE.md", "Classes/AGENTS.md", "Classes/CLAUDE.md", "Documentation/AGENTS.md", "Documentation/CLAUDE.md", "Resources/AGENTS.md", "Resources/CLAUDE.md", "Tests/AGENTS.md", "Tests/CLAUDE.md" ], "agent_instruction_max_bytes": 11003 }, "dependencies": { "manifests": [ "composer.json", "package.json" ], "advisories": { "error": "No resolved dependencies carried a version and a supported ecosystem", "scope": "repository_graph", "source": null, "findings": [], "collected": false, "malicious": [], "truncated": false, "by_severity": {}, "advisory_count": 0, "affected_count": 0, "assessed_count": 0, "malicious_count": 0, "assessed_package": null, "unassessed_count": 22, "direct_affected_count": 0 }, "ecosystems": [ "npm", "packagist" ], "dependencies": [ { "name": "typo3/cms-core", "manifest": "composer.json", "ecosystem": "packagist", "version_constraint": "^12.4 || ^13.4 || ^14.3" }, { "name": "typo3/cms-backend", "manifest": "composer.json", "ecosystem": "packagist", "version_constraint": "^12.4 || ^13.4 || ^14.3" }, { "name": "typo3/cms-setup", "manifest": "composer.json", "ecosystem": "packagist", "version_constraint": "^12.4 || ^13.4 || ^14.3" }, { "name": "web-auth/webauthn-lib", "manifest": "composer.json", "ecosystem": "packagist", "version_constraint": "^5.3" } ], "all_dependencies": { "error": null, "source": "github-sbom", "packages": [ { "name": "typo3/cms-backend", "direct": true, "version": null, "ecosystem": "packagist" }, { "name": "typo3/cms-core", "direct": true, "version": null, "ecosystem": "packagist" }, { "name": "typo3/cms-setup", "direct": true, "version": null, "ecosystem": "packagist" }, { "name": "web-auth/webauthn-lib", "direct": true, "version": null, "ecosystem": "packagist" }, { "name": "@playwright/test", "direct": false, "version": "^1.50", "ecosystem": "npm" }, { "name": "@vitest/coverage-v8", "direct": false, "version": "^4.0", "ecosystem": "npm" }, { "name": "jsdom", "direct": false, "version": "^29.0", "ecosystem": "npm" }, { "name": "vitest", "direct": false, "version": "^4.0", "ecosystem": "npm" }, { "name": "dg/bypass-finals", "direct": false, "version": null, "ecosystem": "packagist" }, { "name": "ergebnis/phpstan-rules", "direct": false, "version": null, "ecosystem": "packagist" }, { "name": "friendsofphp/php-cs-fixer", "direct": false, "version": null, "ecosystem": "packagist" }, { "name": "netresearch/typo3-ci-workflows", "direct": false, "version": null, "ecosystem": "packagist" }, { "name": "php", "direct": false, "version": null, "ecosystem": "packagist" }, { "name": "phpat/phpat", "direct": false, "version": null, "ecosystem": "packagist" }, { "name": "phpstan/extension-installer", "direct": false, "version": null, "ecosystem": "packagist" }, { "name": "phpstan/phpstan", "direct": false, "version": null, "ecosystem": "packagist" }, { "name": "phpstan/phpstan-deprecation-rules", "direct": false, "version": null, "ecosystem": "packagist" }, { "name": "phpstan/phpstan-phpunit", "direct": false, "version": null, "ecosystem": "packagist" }, { "name": "phpstan/phpstan-strict-rules", "direct": false, "version": null, "ecosystem": "packagist" }, { "name": "phpunit/phpunit", "direct": false, "version": null, "ecosystem": "packagist" }, { "name": "typo3/cms-dashboard", "direct": false, "version": null, "ecosystem": "packagist" }, { "name": "typo3/testing-framework", "direct": false, "version": null, "ecosystem": "packagist" } ], "collected": true, "truncated": false, "total_count": 22, "direct_count": 4, "indirect_count": 18 } }, "maintainership": { "issues": { "open_prs": 1, "merged_prs": 100, "open_issues": 0, "closed_ratio": 1, "closed_issues": 4, "closed_unmerged_prs": 5 }, "bus_factor": 1, "bot_contributors": 1, "top_contributors": [ { "type": "User", "login": "CybotTM", "commits": 443, "avatar_url": "https://avatars.githubusercontent.com/u/326348?v=4" }, { "type": "User", "login": "just-tobi", "commits": 10, "avatar_url": "https://avatars.githubusercontent.com/u/5242689?v=4" } ], "contributors_sampled": 2, "top_contributor_share": 0.978 }, "quality_signals": { "has_ci": true, "has_tests": true, "ci_workflows": [ "auto-merge-deps.yml", "check-template-drift.yml", "checks.yml", "ci.yml", "codeql.yml", "community.yml", "ddev-hardening.yml", "dependency-review.yml", "docs.yml", "labeler.yml", "pr-quality.yml", "release.yml", "republish.yml", "scorecard.yml" ], "has_docs_dir": true, "linter_configs": [ ".php-cs-fixer.php", "phpstan.neon" ], "has_editorconfig": true, "has_linter_config": true, "has_precommit_config": false }, "security_signals": { "lockfiles": [], "scorecard": { "checks": [ { "name": "Binary-Artifacts", "score": 10, "reason": "no binaries found in the repo", "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#binary-artifacts" }, { "name": "Branch-Protection", "score": 4, "reason": "branch protection is not maximal on development and all release branches", "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#branch-protection" }, { "name": "CI-Tests", "score": 10, "reason": "13 out of 13 merged PRs checked by a CI test -- score normalized to 10", "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#ci-tests" }, { "name": "CII-Best-Practices", "score": 7, "reason": "badge detected: Silver", "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#cii-best-practices" }, { "name": "Code-Review", "score": 10, "reason": "all changesets reviewed", "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#code-review" }, { "name": "Contributors", "score": 10, "reason": "project has 3 contributing companies or organizations -- score normalized to 10", "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#contributors" }, { "name": "Dangerous-Workflow", "score": null, "reason": "no workflows found", "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#dangerous-workflow" }, { "name": "Dependency-Update-Tool", "score": 10, "reason": "update tool detected", "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#dependency-update-tool" }, { "name": "Fuzzing", "score": 0, "reason": "project is not fuzzed", "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#fuzzing" }, { "name": "License", "score": 10, "reason": "license file detected", "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#license" }, { "name": "Maintained", "score": 10, "reason": "30 commit(s) and 2 issue activity found in the last 90 days -- score normalized to 10", "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#maintained" }, { "name": "Packaging", "score": null, "reason": "packaging workflow not detected", "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#packaging" }, { "name": "Pinned-Dependencies", "score": null, "reason": "no dependencies found", "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#pinned-dependencies" }, { "name": "SAST", "score": 10, "reason": "SAST tool is run on all commits", "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#sast" }, { "name": "Security-Policy", "score": 10, "reason": "security policy file detected", "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#security-policy" }, { "name": "Signed-Releases", "score": 8, "reason": "5 out of the last 5 releases have a total of 5 signed artifacts.", "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#signed-releases" }, { "name": "Token-Permissions", "score": null, "reason": "No tokens found", "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#token-permissions" }, { "name": "Vulnerabilities", "score": 10, "reason": "0 existing vulnerabilities detected", "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#vulnerabilities" } ], "commit": "f23f92063078d5272ef1206c9ae1ee93539b298f", "ran_at": "2026-08-05T20:22:04Z", "aggregate_score": 8.5, "scorecard_version": "v5.5.0" }, "has_codeql_workflow": true, "has_security_policy": true, "has_dependabot_config": true }, "contribution_flow": { "collected": true, "recent_prs": { "merged_7d": 17, "decided_7d": 17, "merged_30d": 27, "authors_30d": 1, "decided_30d": 28, "sample_size": 60, "window_days": 30, "sample_exhausted": false, "authors_probed_30d": 1, "newcomer_merged_30d": 0, "bot_prs_excluded_30d": 0, "newcomer_authors_30d": 0, "newcomer_decided_30d": 0 }, "ci_last_run_at": "2026-08-05T17:39:08Z", "oldest_open_prs": [ { "number": 108, "created_at": "2026-08-05T08:30:14Z", "last_comment_at": "2026-08-05T20:12:13Z", "last_comment_author": "sonarqubecloud" } ], "last_merged_pr_at": "2026-08-05T17:18:36Z", "ci_last_conclusion": "SUCCESS", "oldest_open_issues": [] } }, "config": { "disabled_metrics": [], "disabled_categories": [], "disabled_components": {} }, "source": { "url": "https://github.com/netresearch/t3x-nr-passkeys-be", "host": "github.com", "name": "t3x-nr-passkeys-be", "owner": "netresearch" }, "metrics": { "overall": { "key": "overall", "band": "excellent", "name": "Overall health", "note": "The weighted overall 74 is calibrated to 88 on the published index scale (record calibration 2026-08-02).", "notes": [ { "code": "overall_calibration", "params": { "raw": 74, "calibrated": 88, "calibration": "2026-08-02" } } ], "value": 88, "inputs": { "security": 85, "vitality": 85, "community": 46, "governance": 68, "calibration": "2026-08-02", "engineering": 84, "ai_readiness": 74, "weighted_overall_raw": 74 }, "components": [] }, "categories": [ { "key": "vitality", "band": "excellent", "name": "Vitality", "value": 85, "weight": 0.21, "metrics": [ { "key": "development_activity", "band": "good", "name": "Development activity", "note": null, "notes": [], "value": 77, "inputs": { "commits_last_year": 452, "human_commit_share": 1, "days_since_last_push": 0, "active_weeks_last_year": 19 }, "components": [ { "key": "push_recency", "name": "Push recency", "detail": "last push 0 days ago", "points": 36, "status": "met", "details": [ { "code": "push_recency", "params": { "days": 0 } } ], "max_points": 36 }, { "key": "commit_cadence", "name": "Commit cadence", "detail": "19/52 weeks with commits", "points": 13.2, "status": "partial", "details": [ { "code": "commit_cadence_weeks", "params": { "weeks": 19 } } ], "max_points": 36 }, { "key": "commit_volume", "name": "Commit volume", "detail": "452 commits in the last year", "points": 18, "status": "met", "details": [ { "code": "commits_last_year", "params": { "count": 452 } } ], "max_points": 18 }, { "key": "openssf_scorecard_maintained", "name": "OpenSSF Scorecard: Maintained", "detail": "30 commit(s) and 2 issue activity found in the last 90 days -- score normalized to 10", "points": 10, "status": "met", "details": [], "max_points": 10 } ] }, { "key": "release_discipline", "band": "exceptional", "name": "Release discipline", "note": null, "notes": [], "value": 98, "inputs": { "releases_count": 22, "latest_release_tag": "v0.12.0", "releases_from_tags": false, "days_since_latest_release": 12, "mean_days_between_releases": 6.3 }, "components": [ { "key": "ships_releases", "name": "Ships releases", "detail": "22 releases published", "points": 27, "status": "met", "details": [ { "code": "releases_published", "params": { "count": 22 } } ], "max_points": 27 }, { "key": "release_recency", "name": "Release recency", "detail": "latest release 12 days ago", "points": 36, "status": "met", "details": [ { "code": "release_recency", "params": { "days": 12 } } ], "max_points": 36 }, { "key": "release_cadence", "name": "Release cadence", "detail": "a release every ~6.3 days", "points": 27, "status": "met", "details": [ { "code": "release_cadence", "params": { "gap": 6.3 } } ], "max_points": 27 }, { "key": "openssf_scorecard_signed_releases", "name": "OpenSSF Scorecard: Signed-Releases", "detail": "5 out of the last 5 releases have a total of 5 signed artifacts.", "points": 8, "status": "partial", "details": [], "max_points": 10 } ] }, { "key": "abandonment", "band": "exceptional", "name": "Abandonment", "note": null, "notes": [], "value": 100, "inputs": { "cap": null, "state": "unverified", "guards": [], "signals": [], "red_flag": false, "multiplier_pct": 100, "declared_reason": null, "unverified_reason": "repository_too_young", "unanswered_open_prs": null, "unanswered_open_issues": null, "days_since_last_merged_pr": null, "days_since_last_human_commit": null, "days_since_last_human_commit_is_floor": false }, "components": [ { "key": "project_is_still_maintained", "name": "Project is still maintained", "detail": "maintenance record not established from the collected data", "points": 100, "status": "met", "details": [ { "code": "abandonment_unverified", "params": {} } ], "max_points": 100 } ] } ], "description": "Is the project alive — is code being written and are releases shipping?" }, { "key": "community", "band": "weak", "name": "Community & Adoption", "value": 46, "weight": 0.17, "metrics": [ { "key": "popularity", "band": "critical", "name": "Popularity & adoption", "note": null, "notes": [], "value": 5, "inputs": { "forks": 0, "stars": 3, "watchers": 1, "growth_state": "unverified", "growth_factor_pct": 100, "growth_unverified_reason": "no_history" }, "components": [ { "key": "stars", "name": "Stars", "detail": "3 stars", "points": 4.9, "status": "partial", "details": [ { "code": "stars", "params": { "count": 3 } } ], "max_points": 60 }, { "key": "forks", "name": "Forks", "detail": "0 forks", "points": 0, "status": "missed", "details": [ { "code": "forks", "params": { "count": 0 } } ], "max_points": 25 }, { "key": "watchers", "name": "Watchers", "detail": "1 watchers", "points": 0, "status": "missed", "details": [ { "code": "watchers", "params": { "count": 1 } } ], "max_points": 15 } ] }, { "key": "community_health", "band": "excellent", "name": "Community health", "note": null, "notes": [], "value": 92, "inputs": { "has_readme": true, "has_license": true, "readme_badges": 10, "has_contributing": true, "has_issue_template": false, "has_code_of_conduct": true, "readme_badge_services": [ "api.securityscorecards.dev", "codecov.io", "github.com", "shields.io", "www.bestpractices.dev" ], "has_pull_request_template": true }, "components": [ { "key": "readme", "name": "README", "detail": null, "points": 22.5, "status": "met", "details": [], "max_points": 22.5 }, { "key": "license", "name": "License", "detail": "recognized license (GPL-2.0)", "points": 22.5, "status": "met", "details": [ { "code": "license_standard", "params": {} }, { "code": "license_spdx", "params": { "spdx": "GPL-2.0" } } ], "max_points": 22.5 }, { "key": "contributing_guide", "name": "CONTRIBUTING guide", "detail": null, "points": 18, "status": "met", "details": [], "max_points": 18 }, { "key": "code_of_conduct", "name": "Code of conduct", "detail": null, "points": 13.5, "status": "met", "details": [], "max_points": 13.5 }, { "key": "issue_template", "name": "Issue template", "detail": null, "points": 0, "status": "missed", "details": [], "max_points": 7.2 }, { "key": "pr_template", "name": "PR template", "detail": null, "points": 6.3, "status": "met", "details": [], "max_points": 6.3 } ] }, { "key": "ecosystem_adoption", "band": "weak", "name": "Ecosystem adoption (downloads)", "note": null, "notes": [], "value": 46, "inputs": { "packages": [ "netresearch/nr-passkeys-be" ], "dependents": 1, "ecosystems": "packagist", "total_downloads": 5516, "monthly_downloads": 2161 }, "components": [ { "key": "monthly_downloads", "name": "Monthly downloads", "detail": "2,161 downloads/month across packagist", "points": 44.5, "status": "partial", "details": [ { "code": "downloads_monthly", "params": { "count": 2161, "ecosystems": "packagist" } } ], "max_points": 80 }, { "key": "registry_dependents", "name": "Registry dependents", "detail": "1 packages depend on it", "points": 2, "status": "partial", "details": [ { "code": "registry_dependents", "params": { "count": 1 } } ], "max_points": 20 } ] } ], "description": "Does the project have users, downloads, attention, and a welcoming setup for contributors?" }, { "key": "governance", "band": "good", "name": "Sustainability & Governance", "value": 68, "weight": 0.23, "metrics": [ { "key": "maintainer_resilience", "band": "at_risk", "name": "Maintainer resilience (bus factor)", "note": null, "notes": [], "value": 22, "inputs": { "bus_factor": 1, "contributors_sampled": 2, "top_contributor_share": 0.978 }, "components": [ { "key": "bus_factor", "name": "Bus factor", "detail": "1 contributor(s) cover half of all commits", "points": 9, "status": "partial", "details": [ { "code": "bus_factor", "params": { "count": 1 } } ], "max_points": 54 }, { "key": "commit_distribution", "name": "Commit distribution", "detail": "top contributor authored 98% of commits", "points": 0.5, "status": "partial", "details": [ { "code": "top_contributor_share", "params": { "share": 98 } } ], "max_points": 22.5 }, { "key": "contributor_breadth", "name": "Contributor breadth", "detail": "2 contributors", "points": 2.7, "status": "partial", "details": [ { "code": "contributors_sampled", "params": { "count": 2 } } ], "max_points": 13.5 }, { "key": "openssf_scorecard_contributors", "name": "OpenSSF Scorecard: Contributors", "detail": "project has 3 contributing companies or organizations -- score normalized to 10", "points": 10, "status": "met", "details": [], "max_points": 10 } ] }, { "key": "responsiveness", "band": "exceptional", "name": "Issue & PR responsiveness", "note": "Excluded from scoring (no data or not applicable): Newcomer PR acceptance. Remaining weights renormalized.", "notes": [ { "code": "excluded_no_data", "params": { "components": [ "newcomer_pr_acceptance" ] } }, { "code": "weights_renormalized", "params": {} } ], "value": 98, "inputs": { "merged_prs": 100, "open_issues": 0, "closed_issues": 4, "prs_merged_7d": 17, "prs_decided_7d": 17, "prs_merged_30d": 27, "prs_decided_30d": 28, "issue_closed_ratio": 1, "closed_unmerged_prs": 5, "first_time_authors_30d": 0, "first_time_prs_merged_30d": 0, "first_time_prs_decided_30d": 0 }, "components": [ { "key": "issue_resolution", "name": "Issue resolution", "detail": "100% of issues closed", "points": 42, "status": "met", "details": [ { "code": "issues_closed_share", "params": { "share": 100 } } ], "max_points": 42 }, { "key": "pr_acceptance", "name": "PR acceptance", "detail": "100/105 decided PRs merged", "points": 28.6, "status": "partial", "details": [ { "code": "decided_prs_merged", "params": { "merged": 100, "decided": 105 } } ], "max_points": 30 }, { "key": "newcomer_pr_acceptance", "name": "Newcomer PR acceptance", "detail": "no first-time contributor's PR decided in 30d", "points": 0, "status": "excluded", "details": [ { "code": "no_newcomer_prs", "params": { "days": 30 } } ], "max_points": 13 }, { "key": "openssf_scorecard_code_review", "name": "OpenSSF Scorecard: Code-Review", "detail": "all changesets reviewed", "points": 15, "status": "met", "details": [], "max_points": 15 } ] }, { "key": "stewardship", "band": "good", "name": "Ownership & stewardship", "note": null, "notes": [], "value": 66, "inputs": { "followers": 39, "owner_type": "Organization", "is_verified": null, "owner_login": "netresearch", "public_repos": 280, "account_age_days": 6112 }, "components": [ { "key": "ownership_backing", "name": "Ownership backing", "detail": "organization-owned", "points": 30, "status": "met", "details": [ { "code": "owner_organization", "params": {} } ], "max_points": 30 }, { "key": "verified_domain", "name": "Verified domain", "detail": null, "points": 0, "status": "missed", "details": [], "max_points": 20 }, { "key": "owner_reach", "name": "Owner reach", "detail": "39 followers of netresearch", "points": 11.5, "status": "partial", "details": [ { "code": "owner_followers", "params": { "count": 39, "login": "netresearch" } } ], "max_points": 25 }, { "key": "track_record", "name": "Track record", "detail": "280 public repos, account ~16 yr old", "points": 25, "status": "met", "details": [ { "code": "public_repos", "params": { "count": 280 } }, { "code": "account_age_years", "params": { "years": 16 } } ], "max_points": 25 } ] }, { "key": "package_maintenance", "band": "exceptional", "name": "Package maintenance", "note": null, "notes": [], "value": 100, "inputs": { "packages": [ "netresearch/nr-passkeys-be" ], "ecosystems": "packagist", "any_deprecated": false, "min_days_since_publish": 12 }, "components": [ { "key": "published_resolvable", "name": "Published & resolvable", "detail": "1 package(s) on packagist", "points": 25, "status": "met", "details": [ { "code": "packages_published", "params": { "count": 1, "ecosystems": "packagist" } } ], "max_points": 25 }, { "key": "publish_recency", "name": "Publish recency", "detail": "latest publish 12 days ago", "points": 35, "status": "met", "details": [ { "code": "publish_recency", "params": { "days": 12 } } ], "max_points": 35 }, { "key": "version_history", "name": "Version history", "detail": "23 published versions", "points": 20, "status": "met", "details": [ { "code": "published_versions", "params": { "count": 23 } } ], "max_points": 20 }, { "key": "not_deprecated", "name": "Not deprecated", "detail": "active, not deprecated or yanked", "points": 20, "status": "met", "details": [ { "code": "package_not_deprecated", "params": {} } ], "max_points": 20 } ] } ], "description": "Will the project survive its people — bus factor, responsiveness, who backs it, and package upkeep?" }, { "key": "engineering", "band": "excellent", "name": "Engineering Quality", "value": 84, "weight": 0.19, "metrics": [ { "key": "engineering_practices", "band": "excellent", "name": "Engineering practices", "note": null, "notes": [], "value": 90, "inputs": { "has_ci": true, "has_tests": true, "has_editorconfig": true, "has_linter_config": true, "has_precommit_config": false }, "components": [ { "key": "ci_workflows", "name": "CI workflows", "detail": "14 workflow(s)", "points": 24, "status": "met", "details": [ { "code": "ci_workflows", "params": { "count": 14 } } ], "max_points": 24 }, { "key": "tests_present", "name": "Tests present", "detail": null, "points": 24, "status": "met", "details": [], "max_points": 24 }, { "key": "linter_config", "name": "Linter config", "detail": ".php-cs-fixer.php, phpstan.neon", "points": 16, "status": "met", "details": [ { "code": "file_list", "params": { "files": ".php-cs-fixer.php, phpstan.neon" } } ], "max_points": 16 }, { "key": "pre_commit_hooks", "name": "Pre-commit hooks", "detail": null, "points": 0, "status": "missed", "details": [], "max_points": 9.6 }, { "key": "editorconfig", "name": ".editorconfig", "detail": null, "points": 6.4, "status": "met", "details": [], "max_points": 6.4 }, { "key": "openssf_scorecard_ci_tests", "name": "OpenSSF Scorecard: CI-Tests", "detail": "13 out of 13 merged PRs checked by a CI test -- score normalized to 10", "points": 20, "status": "met", "details": [], "max_points": 20 } ] }, { "key": "documentation", "band": "good", "name": "Documentation", "note": null, "notes": [], "value": 75, "inputs": { "topics": [ "authentication", "fido2", "passkeys", "passwordless", "php", "typo3", "typo3-extension", "webauthn" ], "has_wiki": false, "homepage": null, "has_readme": true, "has_docs_dir": true, "has_description": true }, "components": [ { "key": "readme", "name": "README", "detail": null, "points": 30, "status": "met", "details": [], "max_points": 30 }, { "key": "documentation_directory", "name": "Documentation directory", "detail": null, "points": 25, "status": "met", "details": [], "max_points": 25 }, { "key": "documentation_homepage_site", "name": "Documentation / homepage site", "detail": null, "points": 0, "status": "missed", "details": [], "max_points": 15 }, { "key": "repository_description", "name": "Repository description", "detail": null, "points": 10, "status": "met", "details": [], "max_points": 10 }, { "key": "topics", "name": "Topics", "detail": "8 topics", "points": 10, "status": "met", "details": [ { "code": "topics_count", "params": { "count": 8 } } ], "max_points": 10 }, { "key": "wiki", "name": "Wiki", "detail": null, "points": 0, "status": "missed", "details": [], "max_points": 10 } ] } ], "description": "Are baseline engineering and documentation practices in place?" }, { "key": "security", "band": "excellent", "name": "Security", "value": 85, "weight": 0.16, "metrics": [ { "key": "security_posture", "band": "excellent", "name": "Security posture", "note": "Excluded from scoring (no data or not applicable): Dangerous-Workflow, Packaging, Pinned-Dependencies, Token-Permissions. Remaining weights renormalized.", "notes": [ { "code": "excluded_no_data", "params": { "components": [ "dangerous_workflow", "packaging", "pinned_dependencies", "token_permissions" ] } }, { "code": "weights_renormalized", "params": {} } ], "value": 85, "inputs": { "source": "openssf_scorecard", "checks_evaluated": 14, "scorecard_version": "v5.5.0", "checks_inconclusive": 4, "scorecard_aggregate": 8.5 }, "components": [ { "key": "binary_artifacts", "name": "Binary-Artifacts", "detail": "no binaries found in the repo", "points": 7.5, "status": "met", "details": [], "max_points": 7.5 }, { "key": "branch_protection", "name": "Branch-Protection", "detail": "branch protection is not maximal on development and all release branches", "points": 3, "status": "partial", "details": [], "max_points": 7.5 }, { "key": "ci_tests", "name": "CI-Tests", "detail": "13 out of 13 merged PRs checked by a CI test -- score normalized to 10", "points": 2.5, "status": "met", "details": [], "max_points": 2.5 }, { "key": "cii_best_practices", "name": "CII-Best-Practices", "detail": "badge detected: Silver", "points": 1.8, "status": "partial", "details": [], "max_points": 2.5 }, { "key": "code_review", "name": "Code-Review", "detail": "all changesets reviewed", "points": 7.5, "status": "met", "details": [], "max_points": 7.5 }, { "key": "contributors", "name": "Contributors", "detail": "project has 3 contributing companies or organizations -- score normalized to 10", "points": 2.5, "status": "met", "details": [], "max_points": 2.5 }, { "key": "dangerous_workflow", "name": "Dangerous-Workflow", "detail": "no workflows found", "points": 0, "status": "excluded", "details": [ { "code": "no_data", "params": {} } ], "max_points": 10 }, { "key": "dependency_update_tool", "name": "Dependency-Update-Tool", "detail": "update tool detected", "points": 7.5, "status": "met", "details": [], "max_points": 7.5 }, { "key": "fuzzing", "name": "Fuzzing", "detail": "project is not fuzzed", "points": 0, "status": "missed", "details": [], "max_points": 5 }, { "key": "license", "name": "License", "detail": "license file detected", "points": 2.5, "status": "met", "details": [], "max_points": 2.5 }, { "key": "maintained", "name": "Maintained", "detail": "30 commit(s) and 2 issue activity found in the last 90 days -- score normalized to 10", "points": 7.5, "status": "met", "details": [], "max_points": 7.5 }, { "key": "packaging", "name": "Packaging", "detail": "packaging workflow not detected", "points": 0, "status": "excluded", "details": [ { "code": "no_data", "params": {} } ], "max_points": 5 }, { "key": "pinned_dependencies", "name": "Pinned-Dependencies", "detail": "no dependencies found", "points": 0, "status": "excluded", "details": [ { "code": "no_data", "params": {} } ], "max_points": 5 }, { "key": "sast", "name": "SAST", "detail": "SAST tool is run on all commits", "points": 5, "status": "met", "details": [], "max_points": 5 }, { "key": "security_policy", "name": "Security-Policy", "detail": "security policy file detected", "points": 5, "status": "met", "details": [], "max_points": 5 }, { "key": "signed_releases", "name": "Signed-Releases", "detail": "5 out of the last 5 releases have a total of 5 signed artifacts.", "points": 6, "status": "partial", "details": [], "max_points": 7.5 }, { "key": "token_permissions", "name": "Token-Permissions", "detail": "No tokens found", "points": 0, "status": "excluded", "details": [ { "code": "no_data", "params": {} } ], "max_points": 7.5 }, { "key": "vulnerabilities", "name": "Vulnerabilities", "detail": "0 existing vulnerabilities detected", "points": 7.5, "status": "met", "details": [], "max_points": 7.5 } ] }, { "key": "high_risk_jurisdiction_exposure", "band": "exceptional", "name": "High-Risk Jurisdiction Exposure", "note": "Only high-confidence self-published location evidence affects this multiplier. Ambiguous matches are review-only; country evidence is not proof of nationality, citizenship, legal registration, malicious intent, or sanctions status.", "notes": [ { "code": "jurisdiction_evidence_limits", "params": {} } ], "value": 100, "inputs": { "meaning": "self-published location evidence; not nationality or citizenship", "red_flag": false, "exposures": [], "policy_countries": [ "Russia", "Iran", "North Korea" ], "commit_weight_rule": { "min_commits": 50, "min_commit_share": 0.1 }, "review_only_matches": 0, "below_threshold_exposures": [], "assessed_self_published_locations": 4 }, "components": [ { "key": "policy_exposure_multiplier", "name": "Policy exposure multiplier", "detail": "no confirmed policy-scope location match", "points": 100, "status": "met", "details": [ { "code": "jurisdiction_no_match", "params": {} } ], "max_points": 100 } ] } ], "description": "Are visible security and supply-chain practices strong, with no malicious dependency and no unresolved high-risk jurisdiction exposure?" }, { "key": "ai_readiness", "band": "good", "name": "AI Readiness", "value": 74, "weight": 0.04, "metrics": [ { "key": "ai_agent_context", "band": "excellent", "name": "Agent context & guidance", "note": null, "notes": [], "value": 85, "inputs": { "has_llms_txt": false, "legible_history_share": 0.99, "agent_instruction_files": [ ".ddev/AGENTS.md", ".github/copilot-instructions.md", ".github/workflows/AGENTS.md", ".github/workflows/CLAUDE.md", "AGENTS.md", "CLAUDE.md", "Classes/AGENTS.md", "Classes/CLAUDE.md", "Documentation/AGENTS.md", "Documentation/CLAUDE.md", "Resources/AGENTS.md", "Resources/CLAUDE.md", "Tests/AGENTS.md", "Tests/CLAUDE.md" ], "agent_instruction_max_bytes": 11003 }, "components": [ { "key": "agent_instructions", "name": "Agent instructions", "detail": ".ddev/AGENTS.md, .github/copilot-instructions.md, .github/workflows/AGENTS.md, .github/workflows/CLAUDE.md, AGENTS.md, CLAUDE.md, Classes/AGENTS.md, Classes/CLAUDE.md, Documentation/AGENTS.md, Documentation/CLAUDE.md, Resources/AGENTS.md, Resources/CLAUDE.md, Tests/AGENTS.md, Tests/CLAUDE.md", "points": 45, "status": "met", "details": [ { "code": "file_list", "params": { "files": ".ddev/AGENTS.md, .github/copilot-instructions.md, .github/workflows/AGENTS.md, .github/workflows/CLAUDE.md, AGENTS.md, CLAUDE.md, Classes/AGENTS.md, Classes/CLAUDE.md, Documentation/AGENTS.md, Documentation/CLAUDE.md, Resources/AGENTS.md, Resources/CLAUDE.md, Tests/AGENTS.md, Tests/CLAUDE.md" } } ], "max_points": 45 }, { "key": "machine_readable_docs_llms_txt", "name": "Machine-readable docs (llms.txt)", "detail": null, "points": 0, "status": "missed", "details": [], "max_points": 15 }, { "key": "legible_commit_history", "name": "Legible commit history", "detail": "99 of 100 human commits state their intent (structured subject or explanatory body)", "points": 40, "status": "met", "details": [ { "code": "legible_history", "params": { "legible": 99, "sampled": 100 } } ], "max_points": 40 } ] }, { "key": "ai_verify_loop", "band": "good", "name": "Verify loop (build / test / typecheck)", "note": "Excluded from scoring (no data or not applicable): OpenSSF Scorecard: Pinned-Dependencies. Remaining weights renormalized.", "notes": [ { "code": "excluded_no_data", "params": { "components": [ "openssf_scorecard_pinned_dependencies" ] } }, { "code": "weights_renormalized", "params": {} } ], "value": 73, "inputs": { "has_nix": false, "has_tests": true, "lockfiles": [], "has_dockerfile": true, "typed_language": false, "bootstrap_files": [ "Makefile" ], "has_devcontainer": false, "has_linter_config": true, "typecheck_configs": [], "agent_commit_share": 0, "toolchain_manifests": [], "dependency_bot_commit_share": 0 }, "components": [ { "key": "one_command_bootstrap", "name": "One-command bootstrap", "detail": "Makefile", "points": 18, "status": "met", "details": [ { "code": "file_list", "params": { "files": "Makefile" } } ], "max_points": 18 }, { "key": "automated_tests", "name": "Automated tests", "detail": null, "points": 22, "status": "met", "details": [], "max_points": 22 }, { "key": "lint_format_config", "name": "Lint / format config", "detail": ".php-cs-fixer.php, phpstan.neon", "points": 11, "status": "met", "details": [ { "code": "file_list", "params": { "files": ".php-cs-fixer.php, phpstan.neon" } } ], "max_points": 11 }, { "key": "static_type_checking", "name": "Static type checking", "detail": null, "points": 0, "status": "missed", "details": [], "max_points": 11 }, { "key": "reproducible_environment", "name": "Reproducible environment", "detail": "Dockerfile", "points": 10, "status": "met", "details": [ { "code": "file_list", "params": { "files": "Dockerfile" } } ], "max_points": 10 }, { "key": "demonstrated_agent_practice", "name": "Demonstrated agent practice", "detail": "no agent-authored commits among the last 100", "points": 0, "status": "missed", "details": [ { "code": "no_agent_authored_commits", "params": { "sampled": 100 } } ], "max_points": 10 }, { "key": "automated_maintenance", "name": "Automated maintenance", "detail": "dependency automation configured, none observed in the sampled commits", "points": 5, "status": "partial", "details": [ { "code": "dependency_bot_config_only", "params": {} } ], "max_points": 8 }, { "key": "openssf_scorecard_pinned_dependencies", "name": "OpenSSF Scorecard: Pinned-Dependencies", "detail": "no dependencies found", "points": 0, "status": "excluded", "details": [ { "code": "no_data", "params": {} } ], "max_points": 10 } ] }, { "key": "ai_code_legibility", "band": "moderate", "name": "Code legibility for models", "note": null, "notes": [], "value": 55, "inputs": { "primary_language": "PHP", "largest_source_bytes": 66621, "source_files_sampled": 144, "oversized_source_files": 1 }, "components": [ { "key": "type_checkable_code", "name": "Type-checkable code", "detail": "PHP without a type-check config", "points": 0, "status": "missed", "details": [ { "code": "no_typecheck_config_language", "params": { "language": "PHP" } } ], "max_points": 45 }, { "key": "manageable_file_sizes", "name": "Manageable file sizes", "detail": "1/144 source files over 60KB", "points": 54.6, "status": "partial", "details": [ { "code": "oversized_source_files", "params": { "kb": 60, "sampled": 144, "oversized": 1 } } ], "max_points": 55 } ] } ], "description": "How well is the repo equipped to be developed and maintained with AI coding agents? Carries a deliberately small weight: agent tooling is a real maintenance signal, but its absence must never gate the top of the scale (calibration saturates at raw 91, so 100/100 remains reachable with AI Readiness at zero)." } ], "classification": { "top": [ "host-extension" ], "labels": [ "plugin" ], "scores": { "plugin": 10, "network-service": 3 }, "primary": "plugin", "evidence": [ { "tier": "declared", "label": "plugin", "source": "composer.type:typo3-cms-extension", "weight": 10 }, { "tier": "declared", "label": "library", "source": "npm.private", "weight": -6 }, { "tier": "distribution", "label": "library", "source": "registry:packagist", "weight": 6 }, { "tier": "structure", "label": "network-service", "source": "tree.compose", "weight": 3 } ], "artifacts": [ { "path": "composer.json", "labels": [ "plugin" ], "ecosystem": "packagist" }, { "path": "package.json", "labels": [], "ecosystem": "npm" } ], "confidence": "high", "host_extension": true, "runs_as_process": false, "consumed_by_code": false }, "metrics_version": "2.5.0" }, "warnings": [ "Star history unavailable: GitHub GraphQL error: Resource not accessible by personal access token", "No resolved dependencies carried a version and a supported ecosystem" ], "report_type": "repository", "generated_at": "2026-08-05T20:22:22.327074Z", "schema_version": "0.31.0", "badge_url": "https://raw.githubusercontent.com/inspect-software/badges/main/v1/n/netresearch/t3x-nr-passkeys-be.svg", "full_name": "netresearch/t3x-nr-passkeys-be", "license_state": "standard", "license_spdx": "GPL-2.0" }, "repoMeta": null, "notFound": false, "related": [ { "id": 34276, "full_name": "netresearch/t3x-nr-llm", "url": "https://github.com/netresearch/t3x-nr-llm", "description": "The shared AI foundation for TYPO3 — one LLM setup for every extension on your site", "ecosystem": "packagist", "ecosystems": [ "packagist", "npm" ], "primary_language": "PHP", "languages": [ "PHP" ], "topics": [ "ai", "claude", "llm", "openai", "php", "typo3", "typo3-extension", "anthropic", "gemini", "gpt", "embeddings", "ollama", "provider-abstraction", "streaming", "translation", "extension", "chatbot" ], "license_spdx": "GPL-2.0", "license_state": "standard", "stars": 4, "forks": 1, "watchers": 1, "monthly_downloads": 2421, "latest_score": 91, "latest_band": "excellent", "latest_scanned_at": "2026-07-22T06:09:38.371501Z", "has_high_risk_jurisdiction_exposure": false, "has_malicious_dependency": false, "growth_authenticity": "unverified", "abandonment_state": "maintained", "red_flags": [], "icon_url": "", "icon_source_type": null, "badge_url": "" }, { "id": 39573, "full_name": "authelia/authelia", "url": "https://github.com/authelia/authelia", "description": "The Single Sign-On Multi-Factor portal for web apps, now OpenID Certified™", "ecosystem": "go", "ecosystems": [ "go", "npm" ], "primary_language": "Go", "languages": [ "Go", "TypeScript" ], "topics": [ "totp", "ldap", "sso-authentication", "yubikey", "two-factor-authentication", "docker", "kubernetes", "sso", "multifactor", "push-notifications", "mfa", "two-factor", "authentication", "security", "golang", "2fa", "oauth2", "openid-connect", "webauthn", "passkeys" ], "license_spdx": "Apache-2.0", "license_state": "standard", "stars": 28461, "forks": 1449, "watchers": 164, "monthly_downloads": null, "latest_score": 99, "latest_band": "exceptional", "latest_scanned_at": "2026-08-05T02:21:11.280322Z", "has_high_risk_jurisdiction_exposure": false, "has_malicious_dependency": false, "growth_authenticity": "unverified", "abandonment_state": "maintained", "red_flags": [], "icon_url": "/icon/v1/authelia/authelia?v=7f419322-1", "icon_source_type": "homepage", "badge_url": "" }, { "id": 15781, "full_name": "TYPO3-Solr/ext-solr", "url": "https://github.com/TYPO3-Solr/ext-solr", "description": "A TYPO3 extension that integrates the Apache Solr search server with TYPO3 CMS. dkd Internet Service GmbH is developing the extension. Community contributions are welcome. See CONTRIBUTING.md for details.", "ecosystem": "packagist", "ecosystems": [ "packagist" ], "primary_language": "PHP", "languages": [ "PHP", "JavaScript" ], "topics": [ "solr", "typo3-cms", "php", "search", "cms", "cms-extension", "typo3", "typo3-cms-extension", "ext", "backend", "hacktoberfest" ], "license_spdx": "GPL-3.0", "license_state": "standard", "stars": 146, "forks": 269, "watchers": 15, "monthly_downloads": 61092, "latest_score": 89, "latest_band": "excellent", "latest_scanned_at": "2026-07-17T13:02:28.990518Z", "has_high_risk_jurisdiction_exposure": false, "has_malicious_dependency": false, "growth_authenticity": "unverified", "abandonment_state": "unverified", "red_flags": [], "icon_url": "", "icon_source_type": null, "badge_url": "" }, { "id": 16268, "full_name": "cakephp/authentication", "url": "https://github.com/cakephp/authentication", "description": "Authentication plugin for CakePHP. Can also be used in PSR7 based applications.", "ecosystem": "packagist", "ecosystems": [ "packagist", "npm" ], "primary_language": "PHP", "languages": [ "PHP" ], "topics": [ "php", "cakephp", "cakephp-plugin", "authentication", "middleware", "psr7-middleware", "auth" ], "license_spdx": "MIT", "license_state": "standard", "stars": 117, "forks": 104, "watchers": 29, "monthly_downloads": 198860, "latest_score": 93, "latest_band": "exceptional", "latest_scanned_at": "2026-07-17T14:15:36.317646Z", "has_high_risk_jurisdiction_exposure": false, "has_malicious_dependency": false, "growth_authenticity": "unverified", "abandonment_state": "unverified", "red_flags": [], "icon_url": "", "icon_source_type": null, "badge_url": "" }, { "id": 6448, "full_name": "netresearch/t3x-rte_ckeditor_image", "url": "https://github.com/netresearch/t3x-rte_ckeditor_image", "description": "Image support in CKEditor for the TYPO3 ecosystem - by Netresearch", "ecosystem": "packagist", "ecosystems": [ "packagist", "npm" ], "primary_language": "PHP", "languages": [ "PHP", "TypeScript", "Shell", "JavaScript" ], "topics": [ "ckeditor", "ckeditor-plugin", "magic-images", "rte-ckeditor", "typo3", "typo3-extension", "typo3cms-extension" ], "license_spdx": "AGPL-3.0", "license_state": "standard", "stars": 61, "forks": 67, "watchers": 8, "monthly_downloads": 20556, "latest_score": 92, "latest_band": "excellent", "latest_scanned_at": "2026-07-15T07:36:26.865862Z", "has_high_risk_jurisdiction_exposure": false, "has_malicious_dependency": false, "growth_authenticity": "unverified", "abandonment_state": "unverified", "red_flags": [], "icon_url": "", "icon_source_type": null, "badge_url": "" }, { "id": 19015, "full_name": "TYPO3/typo3", "url": "https://github.com/TYPO3/typo3", "description": "The TYPO3 Core - Enterprise Content Management System. Synchronized mirror of https://review.typo3.org/q/project:Packages/TYPO3.CMS", "ecosystem": "packagist", "ecosystems": [ "packagist", "npm" ], "primary_language": "PHP", "languages": [ "PHP" ], "topics": [ "cms", "php", "content-management", "enterprise", "typo3", "content-management-system", "extbase" ], "license_spdx": "GPL-2.0", "license_state": "standard", "stars": 1199, "forks": 707, "watchers": 111, "monthly_downloads": 1520, "latest_score": 91, "latest_band": "excellent", "latest_scanned_at": "2026-07-17T22:36:00.650649Z", "has_high_risk_jurisdiction_exposure": false, "has_malicious_dependency": false, "growth_authenticity": "unverified", "abandonment_state": "unverified", "red_flags": [], "icon_url": "", "icon_source_type": null, "badge_url": "" } ] } }
公开记录
软件健康报告模式 0.31.0 · 指标 2.5.0 · 2026-08-05 20:22 UTC

netresearch / t3x-nr-passkeys-be

TYPO3 extension for passwordless backend authentication via WebAuthn/FIDO2 Passkeys

PHPGPL-2.0★ 3 星标⑂ 0 复刻始于 2026年2月在 GitHub 上查看 ↗
类型插件如何判定

netresearch/t3x-nr-passkeys-be 的健康指数为 100 分中的 88 分,处于「优秀」区间。 其得分最高的类别是Vitality(85/100),最低的是Community & Adoption(46/100)。 最近一次更新在今天。 近期的大部分工作由 1 位贡献者完成。

88
总分 / 100
优秀

软件健康指数

指标归入加权类别,统一采用 1–100 量表。总体分先取类别加权平均,再依据公开记录的分布进行校准,使各等级具有百分位含义;当公开证据触发高风险司法辖区政策时,评级会按政策调整,并设置 34(存在风险)的上限。

88
卓越93-100公开记录中的最高层级(约前 5%);基本满足所有检验标准
优秀80-92各方面均表现强劲;仅有少量不足
良好65-79健康;不足之处有限且可控
中等50-64可接受,但存在明显不足;建议进行审查
薄弱35-49多个领域存在实质性薄弱环节
存在风险20-34存在重大薄弱环节;采用时应保持审慎
危急1-19问题严重(项目被弃置、仅有单一维护者、缺乏基本工程规范)
活力社区与采用可持续性与治理工程质量安全AI 就绪度

评分画像

每条轴代表一个类别。形状比平均值更重要——健康的对象会填满整个图形,而“一峰一谷”式画像意味着某一维度的优势正掩盖另一维度的风险。

加权总体分 74 经校准后在公布的指数量表上为 88(记录校准 2026-08-02)。

所有权

39 关注者280 个公开仓库始于 2009年11月

该仓库由组织支持——共同承担、可问责的托管责任,可延续于任何单一维护者之后。

软件包生态系统

注册表软件包版本月下载量版本数最近发布标签
Packagistnetresearch/nr-passkeys-bev0.12.02,1612312 天前authenticationbackendtypo3passwordlessfido2webauthnpasskeys

按类别列示的指标

活力

项目是否仍有生命——是否仍在编写代码,是否仍在发布版本?

85优秀 · 占总体的 21%
评分方式
36/36推送新近度 — 最近一次推送于 0 天前
13.2/36提交节奏 — 52 周中有 19 周有提交
18/18提交量 — 最近一年 452 次提交
10/10OpenSSF Scorecard:Maintained — 30 commit(s) and 2 issue activity found in the last 90 days -- score normalized to 10
所用输入
commits_last_year452
human_commit_share1
days_since_last_push0
active_weeks_last_year19

发布纪律

98卓越
评分方式
27/27有发布版本 — 已发布 22 个发布版本
36/36发布时效 — 最近一次发布版本于 12 天前
27/27发布节奏 — 约每 6.3 天发布一次
8/10OpenSSF Scorecard:Signed-Releases — 5 out of the last 5 releases have a total of 5 signed artifacts.
所用输入
releases_count22
latest_release_tagv0.12.0
releases_from_tags
days_since_latest_release12
mean_days_between_releases6.3

社区与采用

项目是否拥有用户、下载量与关注度,并具备欢迎贡献者参与的配置?

46薄弱 · 占总体的 17%
评分方式
4.9/60星标 — 3 个星标
0/25复刻 — 0 个复刻
0/15关注者 — 1 位关注者
所用输入
forks0
stars3
watchers1
growth_stateunverified
growth_factor_pct100
growth_unverified_reasonno_history

社区健康

92优秀
评分方式
22.5/22.5README
22.5/22.5许可证 — 可识别的许可证(GPL-2.0)
18/18CONTRIBUTING 指南
13.5/13.5行为准则
0/7.2议题模板
6.3/6.3PR 模板
所用输入
has_readme
has_license
readme_badges10
has_contributing
has_issue_template
has_code_of_conduct
readme_badge_servicesapi.securityscorecards.dev, codecov.io, github.com, shields.io, www.bestpractices.dev
has_pull_request_template
评分方式
44.5/80月度下载量 — packagist 合计每月 2,161 次下载
2/20注册表被依赖数 — 1 个软件包依赖它
所用输入
packagesnetresearch/nr-passkeys-be
dependents1
ecosystemspackagist
total_downloads5,516
monthly_downloads2,161

可持续性与治理

项目能否在其成员之外延续——巴士系数、响应能力、由谁支持,以及软件包的维护状况?

68良好 · 占总体的 23%
评分方式
9/54巴士系数 — 1 位贡献者贡献了半数提交
0.5/22.5提交分布 — 头号贡献者编写了 98% 的提交
2.7/13.5贡献者广度 — 2 位贡献者
10/10OpenSSF Scorecard:Contributors — project has 3 contributing companies or organizations -- score normalized to 10
所用输入
bus_factor1
contributors_sampled2
top_contributor_share0.978
评分方式
42/42议题解决 — 100% 的议题已关闭
28.6/30PR 接受 — 已裁定的 PR 中 100/105 已合并
0/13Newcomer PR acceptance — 30 天内没有首次贡献者的 PR 得到裁决
15/15OpenSSF Scorecard:Code-Review — all changesets reviewed
所用输入
merged_prs100
open_issues0
closed_issues4
prs_merged_7d17
prs_decided_7d17
prs_merged_30d27
prs_decided_30d28
issue_closed_ratio1
closed_unmerged_prs5
first_time_authors_30d0
first_time_prs_merged_30d0
first_time_prs_decided_30d0
已排除计分(无数据或不适用):newcomer_pr_acceptance。 其余权重已重新归一化。
评分方式
30/30所有权背书 — 组织持有
0/20已验证域名
11.5/25所有者影响力 — netresearch 有 39 位关注者
25/25既往记录 — 280 个公开仓库,账户约 16 年
所用输入
followers39
owner_typeOrganization
is_verified
owner_loginnetresearch
public_repos280
account_age_days6,112
评分方式
25/25已发布且可解析 — packagist 上有 1 个软件包
35/35发布时效 — 最近一次发布于 12 天前
20/20版本历史 — 23 个已发布版本
20/20未被弃用 — 活跃,未被弃用或撤回
所用输入
packagesnetresearch/nr-passkeys-be
ecosystemspackagist
any_deprecated
min_days_since_publish12

工程质量

基础的工程与文档实践是否到位?

84优秀 · 占总体的 19%

工程实践

90优秀
评分方式
24/24CI 工作流 — 14 个工作流
24/24存在测试
16/16Linter 配置 — .php-cs-fixer.php, phpstan.neon
0/9.6Pre-commit 钩子
6.4/6.4.editorconfig
20/20OpenSSF Scorecard:CI-Tests — 13 out of 13 merged PRs checked by a CI test -- score normalized to 10
所用输入
has_ci
has_tests
has_editorconfig
has_linter_config
has_precommit_config

文档

75良好
评分方式
30/30README
25/25文档目录
0/15文档 / 主页站点
10/10仓库描述
10/10主题标签 — 8 个主题标签
0/10Wiki
所用输入
topicsauthentication, fido2, passkeys, passwordless, php, typo3, typo3-extension, webauthn
has_wiki
homepage
has_readme
has_docs_dir
has_description

安全

可见的安全与供应链实践是否稳固,且不存在未解决的高风险司法辖区暴露?

85优秀 · 占总体的 16%

安全态势

85优秀
评分方式
7.5/7.5Binary-Artifacts — no binaries found in the repo
3/7.5Branch-Protection — branch protection is not maximal on development and all release branches
2.5/2.5CI-Tests — 13 out of 13 merged PRs checked by a CI test -- score normalized to 10
1.8/2.5CII-Best-Practices — badge detected: Silver
7.5/7.5Code-Review — all changesets reviewed
2.5/2.5Contributors — project has 3 contributing companies or organizations -- score normalized to 10
0/10Dangerous-Workflow — 无数据
7.5/7.5Dependency-Update-Tool — update tool detected
0/5Fuzzing — project is not fuzzed
2.5/2.5许可证 — license file detected
7.5/7.5Maintained — 30 commit(s) and 2 issue activity found in the last 90 days -- score normalized to 10
0/5Packaging — 无数据
0/5Pinned-Dependencies — 无数据
5/5SAST — SAST tool is run on all commits
5/5Security-Policy — security policy file detected
6/7.5Signed-Releases — 5 out of the last 5 releases have a total of 5 signed artifacts.
0/7.5Token-Permissions — 无数据
7.5/7.5Vulnerabilities — 0 existing vulnerabilities detected
所用输入
sourceopenssf_scorecard
checks_evaluated14
scorecard_versionv5.5.0
checks_inconclusive4
scorecard_aggregate8.5
已排除计分(无数据或不适用):dangerous_workflow, packaging, pinned_dependencies, token_permissions。 其余权重已重新归一化。

AI 就绪度

该仓库在多大程度上具备与 AI 编码代理协同开发与维护的条件?权重刻意设小(4%):代理工具链是一项真实的维护信号,但完全不具备的仓库仍可达到 100/100。

74良好 · 占总体的 4%
评分方式
45/45代理指令 — .ddev/AGENTS.md, .github/copilot-instructions.md, .github/workflows/AGENTS.md, .github/workflows/CLAUDE.md, AGENTS.md, CLAUDE.md, Classes/AGENTS.md, Classes/CLAUDE.md, Documentation/AGENTS.md, Documentation/CLAUDE.md, Resources/AGENTS.md, Resources/CLAUDE.md, Tests/AGENTS.md, Tests/CLAUDE.md
0/15机器可读文档(llms.txt)
40/40可读的提交历史 — 100 次人类提交中有 99 次说明了意图(结构化标题或解释性正文)
所用输入
has_llms_txt
legible_history_share0.99
agent_instruction_files.ddev/AGENTS.md, .github/copilot-instructions.md, .github/workflows/AGENTS.md, .github/workflows/CLAUDE.md, AGENTS.md, CLAUDE.md, Classes/AGENTS.md, Classes/CLAUDE.md, Documentation/AGENTS.md, Documentation/CLAUDE.md, Resources/AGENTS.md, Resources/CLAUDE.md, Tests/AGENTS.md, Tests/CLAUDE.md
agent_instruction_max_bytes11,003
评分方式
18/18一条命令的引导启动 — Makefile
22/22自动化测试
11/11Lint / 格式化配置 — .php-cs-fixer.php, phpstan.neon
0/11静态类型检查
10/10可复现环境 — Dockerfile
0/10已体现的代理实践 — 最近 100 次提交中没有代理编写的提交
5/8自动化维护 — 已配置依赖自动化,但在抽样提交中未观察到
0/10OpenSSF Scorecard:Pinned-Dependencies — 无数据
所用输入
has_nix
has_tests
lockfiles
has_dockerfile
typed_language
bootstrap_filesMakefile
has_devcontainer
has_linter_config
typecheck_configs
agent_commit_share0
toolchain_manifests
dependency_bot_commit_share0
已排除计分(无数据或不适用):OpenSSF Scorecard:Pinned-Dependencies。 其余权重已重新归一化。
评分方式
0/45可类型检查的代码 — PHP,未配置类型检查
54.6/55可控的文件大小 — 采样的 144 个源文件中有 1 个超过 60KB
所用输入
primary_languagePHP
largest_source_bytes66,621
source_files_sampled144
oversized_source_files1

关键数据

3GitHub 星标
2贡献者
452最近 12 个月提交数
0距最近推送天数
22发布版本数
1巴士系数(bus factor)
0开放议题
npm, Packagist软件包生态系统数

数据采集警告

  • Star history unavailable: GitHub GraphQL error: Resource not accessible by personal access token
  • No resolved dependencies carried a version and a supported ecosystem

更多细节

OpenSSF Scorecard 8.5 / 10
8.5综合

来自开源项目 OpenSSF Scorecard 的独立、工具无关的安全评估。每项检查奖励的是安全实践本身,而非特定供应商的工具。Scorecard 无法判定的检查项标记为 不适用,并从安全评分中剔除(绝不按零分计)。Scorecard v5.5.0 · 2026-08-05 20:22 UTC

10Binary-Artifactsno binaries found in the repo
4Branch-Protectionbranch protection is not maximal on development and all release branches
10CI-Tests13 out of 13 merged PRs checked by a CI test -- score normalized to 10
7CII-Best-Practicesbadge detected: Silver
10Code-Reviewall changesets reviewed
10Contributorsproject has 3 contributing companies or organizations -- score normalized to 10
不适用Dangerous-Workflowno workflows found
10Dependency-Update-Toolupdate tool detected
0Fuzzingproject is not fuzzed
10Licenselicense file detected
10Maintained30 commit(s) and 2 issue activity found in the last 90 days -- score normalized to 10
不适用Packagingpackaging workflow not detected
不适用Pinned-Dependenciesno dependencies found
10SASTSAST tool is run on all commits
10Security-Policysecurity policy file detected
8Signed-Releases5 out of the last 5 releases have a total of 5 signed artifacts.
不适用Token-PermissionsNo tokens found
10Vulnerabilities0 existing vulnerabilities detected
直接依赖 4
注册表软件包版本约束清单文件
Packagisttypo3/cms-core^12.4 || ^13.4 || ^14.3composer.json
Packagisttypo3/cms-backend^12.4 || ^13.4 || ^14.3composer.json
Packagisttypo3/cms-setup^12.4 || ^13.4 || ^14.3composer.json
Packagistweb-auth/webauthn-lib^5.3composer.json
全部依赖 22

来自 GitHub 依赖图的完整解析依赖集合:4 个直接依赖与 18 个间接(传递)软件包。仓库提交锁文件时,传递闭包才是完整的。

注册表软件包版本关系
Packagisttypo3/cms-backend直接
Packagisttypo3/cms-core直接
Packagisttypo3/cms-setup直接
Packagistweb-auth/webauthn-lib直接
npm@playwright/test^1.50间接
npm@vitest/coverage-v8^4.0间接
npmjsdom^29.0间接
npmvitest^4.0间接
Packagistdg/bypass-finals间接
Packagistergebnis/phpstan-rules间接
Packagistfriendsofphp/php-cs-fixer间接
Packagistnetresearch/typo3-ci-workflows间接
Packagistphp间接
Packagistphpat/phpat间接
Packagistphpstan/extension-installer间接
Packagistphpstan/phpstan间接
Packagistphpstan/phpstan-deprecation-rules间接
Packagistphpstan/phpstan-phpunit间接
Packagistphpstan/phpstan-strict-rules间接
Packagistphpunit/phpunit间接
Packagisttypo3/cms-dashboard间接
Packagisttypo3/testing-framework间接
依赖安全公告 未评估

本报告未能完成公告比对:No resolved dependencies carried a version and a supported ecosystem

原始 JSON 报告 机器可读

评分是信号,而非担保。 评分反映的是 GitHub 上公开可见的实践——不是代码审计,也不是安全保证。

缺失数据将被剔除并重新归一化权重,绝不按零分计。方法论已版本化并公开:指标 v2.5.0、模式 v0.31.0—— 完整方法论 · 指标知识库.

单项结果在整体记录中的位置: 汇总统计Packagist.