, 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
TYPO3 extension for passwordless backend authentication via WebAuthn/FIDO2 Passkeys
netresearch/t3x-nr-passkeys-be має індекс здоров’я 88 зі 100, що відповідає смузі «Відмінний». Найвищий показник — Vitality (85/100), найнижчий — Community & Adoption (46/100). Останнє оновлення — сьогодні. Більшість нещодавньої роботи виконує один учасник.
Індекс здоров'я програмного забезпечення Метрики згруповано у зважені категорії на шкалі 1–100. Загальна оцінка починається як їхнє зважене середнє, відкаліброване за розподілом публічного реєстру, тож діапазони мають перцентильний зміст; коли публічні дані активують Політику юрисдикцій високого ризику, рейтинг коригується й отримує верхню межу 34 («У зоні ризику»).
88
Винятковий 93-100 Верхній щабель реєстру (≈ топ-5%); відповідає практично всім перевіреним критеріям
Відмінний 80-92 Сильний за всіма напрямами; незначні прогалини
Добрий 65-79 Здоровий; прогалини обмежені та керовані
Помірний 50-64 Прийнятний, але з помітними прогалинами; рекомендовано перевірку
Слабкий 35-49 Суттєві недоліки в кількох сферах
У зоні ризику 20-34 Суттєві слабкі місця; впровадження потребує обережності
Критичний 1-19 Серйозні проблеми (покинутий, єдиний мейнтейнер, без базової гігієни)
Життєздатність Спільнота та впровадження Сталість та врядування Інженерна якість Безпека Готовність до ШІ Профіль оцінок Кожна вісь — окрема категорія. Форма важить більше, ніж середнє: здоровий об'єкт заповнює всю фігуру, тоді як профіль із піками та провалами означає, що сила в одному вимірі маскує ризик в іншому.
Зважений загальний бал 74 калібровано до 88 за шкалою опублікованого індексу (калібрування реєстру 2026-08-02).
Власність 39 підписників 280 публічних репозиторіїв з лист. 2009 р.
За цим репозиторієм стоїть організація — спільна, підзвітна опіка, здатна пережити будь-якого окремого мейнтейнера.
Пакетні екосистеми
Метрики за категоріями Чи живий проєкт — чи пишеться код і чи виходять релізи?
85 Відмінний · 21% загального індексу
Як обчислюється оцінка 36/36 Свіжість push — останній push 0 дн. тому 13.2/36 Ритм комітів — 19/52 тижнів із комітами 18/18 Обсяг комітів — 452 комітів за останній рік 10/10 OpenSSF Scorecard: Maintained — 30 commit(s) and 2 issue activity found in the last 90 days -- score normalized to 10
Використані вхідні дані
Як обчислюється оцінка 27/27 Випускає релізи — опубліковано 22 релізів 36/36 Свіжість релізів — останній реліз 12 дн. тому 27/27 Ритм релізів — реліз кожні ~6,3 дн. 8/10 OpenSSF Scorecard: Signed-Releases — 5 out of the last 5 releases have a total of 5 signed artifacts.
Використані вхідні дані Чи має проєкт користувачів, завантаження, увагу та влаштовані умови для контриб’юторів?
46 Слабкий · 17% загального індексу
Як обчислюється оцінка 4.9/60 Зірки — 3 зірок 0/25 Форки — 0 форків 0/15 Спостерігачі — 1 спостерігачів
Використані вхідні дані
Як обчислюється оцінка 22.5/22.5 README 22.5/22.5 Ліцензія — визнана ліцензія (GPL-2.0) 18/18 Настанови CONTRIBUTING 13.5/13.5 Кодекс поведінки 0/7.2 Шаблон issue 6.3/6.3 Шаблон PR
Використані вхідні дані
Як обчислюється оцінка 44.5/80 Щомісячні завантаження — 2 161 завантажень/місяць у packagist 2/20 Залежні пакети в реєстрі — залежних пакетів: 1
Використані вхідні дані Чи переживе проєкт своїх людей — бас-фактор, реактивність, хто за ним стоїть і як супроводжуються пакети?
68 Добрий · 23% загального індексу
Як обчислюється оцінка 9/54 Бас-фактор — на 1 контриб’ютор(ів) припадає половина всіх комітів 0.5/22.5 Розподіл комітів — головний контриб’ютор — автор 98% комітів 2.7/13.5 Широта контриб’юторів — 2 контриб’юторів 10/10 OpenSSF Scorecard: Contributors — project has 3 contributing companies or organizations -- score normalized to 10
Використані вхідні дані
Як обчислюється оцінка 42/42 Вирішення issue — закрито 100% issue 28.6/30 Прийняття PR — злито 100/105 вирішених PR 0/13 Newcomer PR acceptance — за 30 дн. не вирішено жодного PR від новачка 15/15 OpenSSF Scorecard: Code-Review — all changesets reviewed
Використані вхідні дані Виключено з оцінювання (немає даних або не застосовно): newcomer_pr_acceptance. Залишкові ваги перенормовано.
Як обчислюється оцінка 30/30 Підтримка власника — у власності організації 0/20 Верифікований домен 11.5/25 Охоплення власника — 39 підписників у netresearch 25/25 Послужний список — 280 публічних репозиторіїв, вік облікового запису ~16 р.
Використані вхідні дані
Як обчислюється оцінка 25/25 Опубліковано й доступно — 1 пакет(ів) у packagist 35/35 Свіжість публікацій — остання публікація 12 дн. тому 20/20 Історія версій — 23 опублікованих версій 20/20 Не застарілий — активний, не deprecated і не yanked
Використані вхідні дані Чи наявні базові інженерні практики та документація?
84 Відмінний · 19% загального індексу
Як обчислюється оцінка 24/24 Процеси CI — 14 процес(ів) CI 24/24 Наявні тести 16/16 Конфігурація лінтера — .php-cs-fixer.php, phpstan.neon 0/9.6 Pre-commit-хуки 6.4/6.4 .editorconfig 20/20 OpenSSF Scorecard: CI-Tests — 13 out of 13 merged PRs checked by a CI test -- score normalized to 10
Використані вхідні дані
Як обчислюється оцінка 30/30 README 25/25 Каталог документації 0/15 Сайт документації / домашня сторінка 10/10 Опис репозиторію 10/10 Теми — 8 тем 0/10 Wiki
Використані вхідні дані Чи міцні видимі практики безпеки й ланцюга постачання, без непослабленої пов’язаності з юрисдикціями високого ризику?
85 Відмінний · 16% загального індексу
Як обчислюється оцінка 7.5/7.5 Binary-Artifacts — no binaries found in the repo 3/7.5 Branch-Protection — branch protection is not maximal on development and all release branches 2.5/2.5 CI-Tests — 13 out of 13 merged PRs checked by a CI test -- score normalized to 10 1.8/2.5 CII-Best-Practices — badge detected: Silver 7.5/7.5 Code-Review — all changesets reviewed 2.5/2.5 Contributors — project has 3 contributing companies or organizations -- score normalized to 10 0/10 Dangerous-Workflow — немає даних 7.5/7.5 Dependency-Update-Tool — update tool detected 0/5 Fuzzing — project is not fuzzed 2.5/2.5 Ліцензія — license file detected 7.5/7.5 Maintained — 30 commit(s) and 2 issue activity found in the last 90 days -- score normalized to 10 0/5 Packaging — немає даних 0/5 Pinned-Dependencies — немає даних 5/5 SAST — SAST tool is run on all commits 5/5 Security-Policy — security policy file detected 6/7.5 Signed-Releases — 5 out of the last 5 releases have a total of 5 signed artifacts. 0/7.5 Token-Permissions — немає даних 7.5/7.5 Vulnerabilities — 0 existing vulnerabilities detected
Використані вхідні дані Виключено з оцінювання (немає даних або не застосовно): dangerous_workflow, packaging, pinned_dependencies, token_permissions. Залишкові ваги перенормовано.
Наскільки репозиторій оснащений для розробки та супроводу за участі ШІ-агентів? Має свідомо малу вагу (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 Читабельна історія комітів — намір зазначено у 99 з 100 людських комітів (структурований заголовок або пояснювальний текст)
Використані вхідні дані
Як обчислюється оцінка 18/18 Розгортання однією командою — Makefile 22/22 Автоматизовані тести 11/11 Конфігурація лінтера / форматера — .php-cs-fixer.php, phpstan.neon 0/11 Статична перевірка типів 10/10 Відтворюване середовище — Dockerfile 0/10 Підтверджена практика роботи з агентами — серед останніх 100 комітів немає створених агентом 5/8 Автоматизоване супроводження — автоматизацію залежностей налаштовано, але у вибірці комітів її не видно 0/10 OpenSSF Scorecard: Pinned-Dependencies — немає даних
Використані вхідні дані Виключено з оцінювання (немає даних або не застосовно): OpenSSF Scorecard: Pinned-Dependencies. Залишкові ваги перенормовано.
Як обчислюється оцінка 0/45 Типізований код — PHP без конфігурації перевірки типів 54.6/55 Керовані розміри файлів — 1/144 файлів вихідного коду понад 60 КБ
Використані вхідні дані
Ключові факти 3 зірок GitHub
2 контриб'юторів
452 комітів за останні 12 місяців
0 днів від останнього пушу
22 релізів
1 бас-фактор
0 відкритих issue
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
Прямі залежності 4 Реєстр Пакет Обмеження версії Маніфест Packagist typo3/cms-core ^12.4 || ^13.4 || ^14.3composer.json Packagist typo3/cms-backend ^12.4 || ^13.4 || ^14.3composer.json Packagist typo3/cms-setup ^12.4 || ^13.4 || ^14.3composer.json Packagist web-auth/webauthn-lib ^5.3composer.json
Усі залежності 22 Повний розв'язаний набір залежностей із графа залежностей GitHub: 4 прямих і 18 непрямих (транзитивних) пакетів. Транзитивне замикання є повним, коли в репозиторії закомічено lockfile.
Реєстр Пакет Версія Зв'язок Packagist typo3/cms-backend —пряма Packagist typo3/cms-core —пряма Packagist typo3/cms-setup —пряма Packagist web-auth/webauthn-lib —пряма npm @playwright/test ^1.50непряма npm @vitest/coverage-v8 ^4.0непряма npm jsdom ^29.0непряма npm vitest ^4.0непряма Packagist dg/bypass-finals —непряма Packagist ergebnis/phpstan-rules —непряма Packagist friendsofphp/php-cs-fixer —непряма Packagist netresearch/typo3-ci-workflows —непряма Packagist php —непряма Packagist phpat/phpat —непряма Packagist phpstan/extension-installer —непряма Packagist phpstan/phpstan —непряма Packagist phpstan/phpstan-deprecation-rules —непряма Packagist phpstan/phpstan-phpunit —непряма Packagist phpstan/phpstan-strict-rules —непряма Packagist phpunit/phpunit —непряма Packagist typo3/cms-dashboard —непряма Packagist typo3/testing-framework —непряма
Сповіщення про залежності не оцінено Звірка сповіщень не відбулася для цього звіту: No resolved dependencies carried a version and a supported ecosystem
Звіт у форматі JSON машиночитний