Informe JSON sin procesar legible por máquina
{
"data": {
"repo": {
"topics": [],
"is_fork": false,
"size_kb": 512,
"has_wiki": true,
"homepage": null,
"languages": {
"JavaScript": 11281,
"TypeScript": 283658
},
"pushed_at": "2026-07-21T14:56:52Z",
"created_at": "2026-03-25T12:12:25Z",
"owner_type": "Organization",
"updated_at": "2026-07-21T15:00:34Z",
"description": null,
"is_archived": false,
"is_disabled": false,
"license_spdx": null,
"default_branch": "main",
"license_spdx_raw": null,
"primary_language": "TypeScript",
"significant_languages": [
"TypeScript"
]
},
"owner": {
"blog": null,
"name": "Encryption for all",
"type": "Organization",
"login": "encryption4all",
"company": null,
"location": null,
"followers": 6,
"avatar_url": "https://avatars.githubusercontent.com/u/78606495?v=4",
"created_at": "2021-02-05T15:03:12Z",
"is_verified": null,
"public_repos": 16,
"account_age_days": 1992
},
"license": {
"state": "absent",
"spdx_id": null,
"raw_spdx": null,
"file_present": false,
"scorecard_found": false,
"profile_has_license": false
},
"activity": {
"releases": [
{
"tag": "v2.3.1",
"kind": "patch",
"published_at": "2026-07-17T05:57:23Z"
},
{
"tag": "v2.3.0",
"kind": "minor",
"published_at": "2026-07-16T18:02:59Z"
},
{
"tag": "v2.2.0",
"kind": "minor",
"published_at": "2026-07-15T07:34:21Z"
},
{
"tag": "v2.1.7",
"kind": "patch",
"published_at": "2026-07-15T07:16:00Z"
},
{
"tag": "v2.1.6",
"kind": "patch",
"published_at": "2026-07-15T07:12:38Z"
},
{
"tag": "v2.1.5",
"kind": "patch",
"published_at": "2026-07-14T14:13:46Z"
},
{
"tag": "v2.1.4",
"kind": "patch",
"published_at": "2026-07-06T13:44:27Z"
},
{
"tag": "v2.1.3",
"kind": "patch",
"published_at": "2026-07-06T13:39:24Z"
},
{
"tag": "v2.1.2",
"kind": "patch",
"published_at": "2026-07-02T11:37:42Z"
},
{
"tag": "v2.1.1",
"kind": "patch",
"published_at": "2026-07-02T11:36:31Z"
},
{
"tag": "v2.1.0",
"kind": "minor",
"published_at": "2026-06-19T12:09:24Z"
},
{
"tag": "v2.0.2",
"kind": "patch",
"published_at": "2026-06-08T12:49:02Z"
},
{
"tag": "v2.0.1",
"kind": "patch",
"published_at": "2026-06-03T09:49:36Z"
},
{
"tag": "v2.0.0",
"kind": "major",
"published_at": "2026-06-02T10:52:46Z"
},
{
"tag": "v1.11.0",
"kind": "minor",
"published_at": "2026-06-02T07:46:35Z"
},
{
"tag": "v1.10.3",
"kind": "patch",
"published_at": "2026-06-02T07:36:39Z"
},
{
"tag": "v1.10.2",
"kind": "patch",
"published_at": "2026-06-02T07:35:28Z"
},
{
"tag": "v1.10.1",
"kind": "patch",
"published_at": "2026-06-02T07:34:23Z"
},
{
"tag": "v1.10.0",
"kind": "minor",
"published_at": "2026-05-24T13:45:10Z"
},
{
"tag": "v1.9.0",
"kind": "minor",
"published_at": "2026-05-24T13:33:37Z"
},
{
"tag": "v1.8.0",
"kind": "minor",
"published_at": "2026-05-16T12:49:11Z"
},
{
"tag": "v1.7.1",
"kind": "patch",
"published_at": "2026-05-13T16:30:29Z"
},
{
"tag": "v1.7.0",
"kind": "minor",
"published_at": "2026-05-13T15:43:25Z"
},
{
"tag": "v1.6.2",
"kind": "patch",
"published_at": "2026-05-12T07:24:14Z"
},
{
"tag": "v1.6.1",
"kind": "patch",
"published_at": "2026-05-12T07:21:56Z"
},
{
"tag": "v1.6.0",
"kind": "minor",
"published_at": "2026-05-07T20:45:17Z"
},
{
"tag": "v1.5.0",
"kind": "minor",
"published_at": "2026-05-07T19:01:42Z"
},
{
"tag": "v1.4.0",
"kind": "minor",
"published_at": "2026-05-07T12:12:49Z"
},
{
"tag": "v1.3.0",
"kind": "minor",
"published_at": "2026-05-06T11:22:20Z"
},
{
"tag": "v1.2.0",
"kind": "minor",
"published_at": "2026-05-02T10:24:25Z"
},
{
"tag": "v1.1.0",
"kind": "minor",
"published_at": "2026-05-01T11:48:35Z"
},
{
"tag": "v1.0.3",
"kind": "patch",
"published_at": "2026-05-01T11:32:38Z"
},
{
"tag": "v1.0.2",
"kind": "patch",
"published_at": "2026-05-01T11:31:34Z"
},
{
"tag": "v1.0.1",
"kind": "patch",
"published_at": "2026-05-01T11:30:26Z"
},
{
"tag": "v0.10.0",
"kind": "minor",
"published_at": "2026-04-24T11:12:09Z"
},
{
"tag": "v0.9.3",
"kind": "patch",
"published_at": "2026-04-21T12:27:53Z"
},
{
"tag": "v0.9.2",
"kind": "patch",
"published_at": "2026-04-21T09:02:01Z"
},
{
"tag": "v0.9.1",
"kind": "patch",
"published_at": "2026-04-16T20:18:48Z"
},
{
"tag": "v0.9.0",
"kind": "minor",
"published_at": "2026-04-16T20:03:15Z"
},
{
"tag": "v0.8.0",
"kind": "minor",
"published_at": "2026-04-16T19:46:25Z"
},
{
"tag": "v0.7.2",
"kind": "patch",
"published_at": "2026-04-16T19:20:50Z"
},
{
"tag": "v0.7.1",
"kind": "patch",
"published_at": "2026-04-14T13:55:31Z"
},
{
"tag": "v0.7.0",
"kind": "minor",
"published_at": "2026-04-10T14:41:20Z"
},
{
"tag": "v0.6.0",
"kind": "minor",
"published_at": "2026-04-10T13:28:35Z"
},
{
"tag": "v0.5.0",
"kind": "minor",
"published_at": "2026-04-09T14:21:55Z"
},
{
"tag": "v0.4.0",
"kind": "minor",
"published_at": "2026-04-09T08:59:25Z"
},
{
"tag": "v0.3.0",
"kind": "minor",
"published_at": "2026-04-09T07:47:03Z"
},
{
"tag": "v0.2.0",
"kind": "minor",
"published_at": "2026-03-30T08:30:09Z"
},
{
"tag": "v0.1.3",
"kind": "patch",
"published_at": "2026-03-25T15:21:15Z"
}
],
"recent_commits": [
{
"oid": "6c0f55443706ba27fda1db73f48655795cb151d9",
"body": "test(encrypt): cover encryptPipeline, sealRaw and awaitAllOrAbort",
"is_bot": false,
"headline": "Merge pull request #117 from encryption4all/test/encrypt-pipeline",
"author_name": "Ruben Hensen",
"author_login": "rubenhensen",
"committed_at": "2026-07-21T14:56:48Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "9e821471ad440126cdb2c5f37331b41047343659",
"body": "The encrypt/upload pipeline in src/crypto/encrypt.ts had no direct test\ncoverage. Add unit tests with sealStream/loadWasm and the Cryptify upload\nsink mocked:\n\n- awaitAllOrAbort: happy path (no abort), first-failure abort + re-throw\n of the first error from either side, loser-rejection is swallowed\n[…]\nciphertext.\n\nawaitAllOrAbort is exported for direct testing; it is not re-exported from\nsrc/index.ts, so the package's public API is unchanged.\n\nCo-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>",
"is_bot": true,
"headline": "test(encrypt): cover encryptPipeline, sealRaw and awaitAllOrAbort",
"author_name": "dobby-yivi-agent[bot]",
"author_login": "dobby-coder[bot]",
"committed_at": "2026-07-17T17:09:23Z",
"body_truncated": true,
"is_coding_agent": true
},
{
"oid": "c7d21aae03cadcf4fbf8e09a7b6beed73d3d6e4f",
"body": "fix: floor the seal policy timestamp so it is never in the future",
"is_bot": false,
"headline": "Merge pull request #116 from encryption4all/fix/seal-timestamp-floor",
"author_name": "Ruben Hensen",
"author_login": "rubenhensen",
"committed_at": "2026-07-17T05:56:27Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "f3169a6004a67b1d7bb749e083f18052e7155463",
"body": "The PKG rejects a USK request whose timestamp is > now (\"chronology error\").\nThe seal timestamp was stamped with Math.round(Date.now()/1000), which can\nland up to ~1s ahead of the real time, so an encrypt→decrypt within the same\nsecond could fail. In normal usage the multi-second gap masks it; the e\n[…]\ne\nharness's headless flow (encryptionall/postguard-e2e) completes in <1s and\nsurfaced it.\n\nExtract nowSeconds() (floor) and use it for both seal timestamps. Adds unit\ntests pinning the floor behavior.",
"is_bot": false,
"headline": "fix: floor the seal policy timestamp so it is never in the future",
"author_name": "Ruben Hensen",
"author_login": "rubenhensen",
"committed_at": "2026-07-17T05:50:30Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "854d5c06ea7c852e8ebb8517687d71f0349bfd51",
"body": "feat: configurable email attribute types (emailAttributes option)",
"is_bot": false,
"headline": "Merge pull request #115 from encryption4all/feat/email-attribute",
"author_name": "Ruben Hensen",
"author_login": "rubenhensen",
"committed_at": "2026-07-16T18:01:57Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "7b1c6287bd8a0b56e53b096c56262603c7918e91",
"body": "Review catch: the apiKey dispatch case dropped the configured attributes and\nfetchSigningKeysWithApiKey hardcoded the production type in its request body\n- under an override, an api-key sender would request the production type\nwhile recipient policies used the overridden one. Threaded through\n(dispa\n[…]\nst capturing the request body. Note: the PKG derives the\napi-key signing identity from the business database and ignores this body\ntoday; the configured type is sent anyway for wire-level consistency.",
"is_bot": false,
"headline": "fix: thread emailAttributes through the api-key signing path too",
"author_name": "Ruben Hensen",
"author_login": "rubenhensen",
"committed_at": "2026-07-16T18:01:24Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "b41fe228daf90c6a7bf831b6a63251fe19049642",
"body": "Part 3 of encryption4all/postguard#236 (the pg-js leg). The pbdf email and\nemail-domain attribute types were hardcoded across recipient builders, key\nrequests, decryption hints, includeSender policies and all signing flows —\nmaking it impossible to run the real SDK in any test environment (pbdf\ncred\n[…]\ns are unaffected — all 212 existing tests pass untouched; 6\nnew tests pin the override and default behavior.\n\nCompanions: encryption4all/postguard#244 (PKG) and\nencryption4all/cryptify#193 (cryptify).",
"is_bot": false,
"headline": "feat: configurable email attribute types (emailAttributes option)",
"author_name": "Ruben Hensen",
"author_login": "rubenhensen",
"committed_at": "2026-07-16T17:52:21Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "f8bd16b154b79b92bb086c2c088f10e79babb7db",
"body": "feat(sign): prepareSign() — pre-warm a Yivi session for one-tap app open on iOS",
"is_bot": false,
"headline": "Merge pull request #103 from encryption4all/feat/prepare-sign-prewarm",
"author_name": "Ruben Hensen",
"author_login": "rubenhensen",
"committed_at": "2026-07-15T07:33:09Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "c4a26c324fc32ae69543043be92a9379f5dee34f",
"body": "docs: extend CLAUDE.md with migrated agent notes",
"is_bot": false,
"headline": "Merge pull request #104 from encryption4all/chore/add-claude-md",
"author_name": "Ruben Hensen",
"author_login": "rubenhensen",
"committed_at": "2026-07-15T07:21:24Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "572c91a5f98c4d9ab4db20fe1fb8ae4866537b20",
"body": "perf(chunker): fill output buffers from chunk views to cut per-chunk copies",
"is_bot": false,
"headline": "Merge pull request #111 from encryption4all/perf/chunker-zero-copy",
"author_name": "Ruben Hensen",
"author_login": "rubenhensen",
"committed_at": "2026-07-15T07:15:02Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "6181086d3b8986a54e543411fb5b7bbfa2a68a73",
"body": "fix(download): cancel previous reader before retrying",
"is_bot": false,
"headline": "Merge pull request #113 from encryption4all/fix/108-download-reader-leak",
"author_name": "Ruben Hensen",
"author_login": "rubenhensen",
"committed_at": "2026-07-15T07:11:27Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "c457be42a94d231c673a675cd6f7dc318d9c0294",
"body": "The pre* hooks run three generators (wasm-base64, yivi-css, version),\nnot two; version.ts is itself a gitignored build-time source.",
"is_bot": true,
"headline": "docs: correct generator count in CLAUDE.md build-pipeline note",
"author_name": "dobby-yivi-agent[bot]",
"author_login": "dobby-coder[bot]",
"committed_at": "2026-07-15T07:00:35Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "7d21ce6ffb51fa3fac00fd53895f61808726e973",
"body": "The migrated 'Repo layout' note described integration.yml as running\n'typecheck, build, test on Node 24', which under-states the actual matrix\n(Node 22 and 24, plus Bun and Deno lanes) and every lane's smoke step. It\nalso contradicted the accurate description already present under 'Releases\nand CI'. Align the two.\n\nCo-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>",
"is_bot": true,
"headline": "docs: correct integration.yml CI description in repo-layout note",
"author_name": "dobby-yivi-agent[bot]",
"author_login": "dobby-coder[bot]",
"committed_at": "2026-07-15T06:55:46Z",
"body_truncated": false,
"is_coding_agent": true
},
{
"oid": "6326ca81c7cb6c410e385205f54e60a3010af702",
"body": "On the retrying download stream's `start()` source, each attempt opened a\nfresh fetch/reader but never released the previous one on a mid-stream\nerror. On a flapping connection every retry left a dangling response-body\nreader holding a lock on an abandoned stream.\n\nHoist `reader` so the `catch` can \n[…]\nping back to a fresh fetch. Add a\nfail-then-succeed regression test asserting the first attempt's reader is\ncancelled exactly once.\n\nFixes #108\n\nCo-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>",
"is_bot": true,
"headline": "fix(download): cancel previous reader before retrying",
"author_name": "dobby-yivi-agent[bot]",
"author_login": "dobby-coder[bot]",
"committed_at": "2026-07-14T23:40:21Z",
"body_truncated": true,
"is_coding_agent": true
},
{
"oid": "b5de5f08a7e83e3475459c0db6c63540b15ba049",
"body": "…copies\n\nChunker copied streamed data three times per emitted chunk: chunk.slice()\nallocated a copy of the slice, which was copied into a reused ArrayBuffer,\nwhich was then copied again into a fresh output buffer before enqueue. Over\nthe whole ciphertext stream on large uploads this is a large amoun\n[…]\nre unchanged.\n\nAdd tests covering data spanning a chunk boundary across multiple transform\ncalls and buffer independence across emitted chunks.\n\nCo-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>",
"is_bot": true,
"headline": "perf(chunker): fill output buffers from chunk views to cut per-chunk …",
"author_name": "dobby-yivi-agent[bot]",
"author_login": "dobby-coder[bot]",
"committed_at": "2026-07-14T23:39:29Z",
"body_truncated": true,
"is_coding_agent": true
},
{
"oid": "7dee2640ec73acf379b9f297ffc50dab8f330201",
"body": null,
"is_bot": false,
"headline": "docs: add agent & contributor notes (migrated from dobby memory)",
"author_name": "Ruben Hensen",
"author_login": "rubenhensen",
"committed_at": "2026-07-14T18:46:56Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "e8a84f79d825926165c1c1adede03699e1bcd92f",
"body": "…app open\n\nOn iOS a Yivi Universal Link only opens the app when the navigation happens\ninside a genuine user gesture. If the signing session is started on tap (as\npart of encrypt()), the app deep-link URL doesn't exist yet, so the tap can't\nnavigate synchronously and falls back to Safari.\n\nprepareSi\n[…]\ns` field; when supplied, Sealed.getSigningKeys() uses it\ndirectly and never starts a second session (the internal pipeline already\nthreaded signingKeys). Adds AbortSignal support to the Yivi resolver.",
"is_bot": false,
"headline": "feat(sign): add prepareSign() to pre-warm a Yivi session for one-tap …",
"author_name": "Ruben Hensen",
"author_login": "rubenhensen",
"committed_at": "2026-07-14T14:38:17Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "b2f55ad9948d3c163e68d97830a6d2fe945ddbb6",
"body": "fix(yivi): use SSE for session status, fall back to polling",
"is_bot": false,
"headline": "Merge pull request #102 from encryption4all/fix/yivi-sse-status-updates",
"author_name": "Ruben Hensen",
"author_login": "rubenhensen",
"committed_at": "2026-07-14T14:12:46Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "f02725a1330b805d3d94b2b0bfaaf393007e0a36",
"body": "The Yivi session state config hardcoded `serverSentEvents: false`, which\nforced yivi-client's StatusListener into polling for every disclosure/\nsigning session. Enable the irmaserver's Server-Sent Events stream\n(/frontend/statusevents) instead — the same default yivi-client ships —\nso status updates\n[…]\nck to\npolling when the SSE connection can't be established.\n\nApplies to both the signing (resolveSigningKeysFromYivi) and decrypt\n(retrieveUSKViaYivi) flows.\n\nRefs encryption4all/postguard-website#317",
"is_bot": false,
"headline": "fix(yivi): use SSE for session status, fall back to polling",
"author_name": "Ruben Hensen",
"author_login": "rubenhensen",
"committed_at": "2026-07-14T14:07:46Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "54dced1bbb1c5c52946335efc4db7cfe4850ee25",
"body": "fix: escape quotes in attachment names and sanitize injected header names",
"is_bot": false,
"headline": "Merge pull request #99 from encryption4all/fix/97-mime-name-escaping",
"author_name": "Ruben Hensen",
"author_login": "rubenhensen",
"committed_at": "2026-07-10T08:36:29Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "e044d7ac3b37bfa16b3545cdd12b164a36de963e",
"body": "fix: harden client-side JWT handling in the Yivi session path",
"is_bot": false,
"headline": "Merge pull request #101 from encryption4all/fix/98-jwt-hardening",
"author_name": "Ruben Hensen",
"author_login": "rubenhensen",
"committed_at": "2026-07-06T13:43:18Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "2a4f2f5b8caf98a5a5c6e73465e7008144b1dc2f",
"body": "…ars-mime-headers\n\nfix: strip control characters from MIME header values",
"is_bot": false,
"headline": "Merge pull request #100 from encryption4all/security/strip-control-ch…",
"author_name": "Ruben Hensen",
"author_login": "rubenhensen",
"committed_at": "2026-07-06T13:38:22Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "d0e47238eb6abdc62a9d9a2835053caaf03567a1",
"body": "Yivi/IRMA session-result JWTs are decoded client-side without signature\nverification, so their claims must not be trusted verbatim.\n\n- New shared util src/util/jwt.ts -> decodeJwtPayloadUnsafe: structural-only\n decode (3 non-empty segments, base64url + UTF-8), returns null on any\n malformation; do\n[…]\nt-provided senderEmail wins over the JWT value.\n\nDefense-in-depth: the PKG server verifies the JWT signature before issuing keys.\n\nResolves #98\n\nCo-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>",
"is_bot": true,
"headline": "fix: harden client-side JWT handling in the Yivi session path",
"author_name": "dobby-yivi-agent[bot]",
"author_login": "dobby-coder[bot]",
"committed_at": "2026-07-05T23:24:59Z",
"body_truncated": true,
"is_coding_agent": true
},
{
"oid": "b4171400a0e9ba3bf143b5e2ab3b6b1affcce927",
"body": "The comment claimed a bare CR was embedded, but no CR byte is present in\nany field; list the actual control bytes (NUL, SOH, backspace, VT, ESC,\nDEL) instead.\n\nCo-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>",
"is_bot": true,
"headline": "test: fix control-char test comment to match embedded bytes",
"author_name": "dobby-yivi-agent[bot]",
"author_login": "dobby-coder[bot]",
"committed_at": "2026-07-05T23:20:49Z",
"body_truncated": false,
"is_coding_agent": true
},
{
"oid": "4c5618263fa2132164f42c4fa7ed57503231829e",
"body": "sanitizeHeaderValue now removes C0 control characters and DEL from\nuser-supplied header values, in addition to collapsing CR/LF runs to a\nsingle space, so no control byte survives interpolation into the MIME\ntemplate. Tab is preserved as valid header whitespace.\n\nAdds regression tests for control-character stripping and tab\npreservation.\n\nCo-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>",
"is_bot": true,
"headline": "fix: strip control characters from MIME header values",
"author_name": "dobby-yivi-agent[bot]",
"author_login": "dobby-coder[bot]",
"committed_at": "2026-07-05T23:10:19Z",
"body_truncated": false,
"is_coding_agent": true
},
{
"oid": "5becc13d72802e783f8f9aaabfd06755558e2d77",
"body": "…ames\n\nTwo related MIME header-injection gaps in src/email/mime.ts (GHSA-qmf2-7wp2-gm9x):\n\n- injectMimeHeaders interpolated header names (and values) from headersToInject\n without stripping CR/LF, allowing extra header lines to be smuggled in. Both\n name and value are now run through sanitizeHeade\n[…]\nnject additional parameters. Added\n escapeQuotedStringParam (backslash + quote escaping after CR/LF folding).\n\nAdds regression tests for both.\n\nCo-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>",
"is_bot": true,
"headline": "fix: escape quotes in attachment names and sanitize injected header n…",
"author_name": "dobby-yivi-agent[bot]",
"author_login": "dobby-coder[bot]",
"committed_at": "2026-07-04T23:06:54Z",
"body_truncated": true,
"is_coding_agent": true
},
{
"oid": "b5c4b19e632101935e273954173613355e80ff92",
"body": "fix: sanitize user input in MIME construction",
"is_bot": false,
"headline": "Merge pull request #96 from encryption4all/fix/93-mime-sanitization",
"author_name": "Ruben Hensen",
"author_login": "rubenhensen",
"committed_at": "2026-07-02T11:36:44Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "7dc6253ddfcce2d81811aad18e7dd85d743521b9",
"body": "fix(envelope): tighten validation of the websiteUrl option",
"is_bot": false,
"headline": "Merge pull request #95 from encryption4all/fix/94-validate-website-url",
"author_name": "Ruben Hensen",
"author_login": "rubenhensen",
"committed_at": "2026-07-02T11:35:38Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "67b1e496197880a1e0760290116f1929e2eaf295",
"body": "chore: update dependencies",
"is_bot": false,
"headline": "Merge pull request #92 from encryption4all/chore/update-dependencies",
"author_name": "Ruben Hensen",
"author_login": "rubenhensen",
"committed_at": "2026-07-02T08:34:08Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "2c884aa31e18743bf221b2f8ae830b127f04b0cc",
"body": "Harden src/email/mime.ts against header injection:\n\n- Strip CR/LF runs from all user-supplied header values (from, to, cc,\n subject, inReplyTo, references, attachment name/type) before they are\n interpolated into the MIME template, collapsing them to a single space\n so a crafted value cannot smug\n[…]\nme\n containing metacharacters is matched literally rather than as a pattern.\n\nAdds tests/mime.test.ts covering both hardening paths.\n\nRefs #93\n\nCo-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>",
"is_bot": true,
"headline": "fix: sanitize user input in MIME construction (GHSA-hvj8-v57h-f99m)",
"author_name": "dobby-yivi-agent[bot]",
"author_login": "dobby-coder[bot]",
"committed_at": "2026-07-02T03:03:33Z",
"body_truncated": true,
"is_coding_agent": true
},
{
"oid": "ab381a55317c61c852b05065c7c2c729bb715471",
"body": "Interpolating options.websiteUrl verbatim into the generated email's\nhref/src attributes allowed javascript:/data: schemes and\nattribute-breaking strings to be injected into the HTML body. Validate\nthat the resolved URL is a well-formed absolute https: URL via new URL()\nand throw otherwise; re-serialize from origin + pathname so the parser's\npercent-encoding neutralizes any attribute-breaking characters.\n\nRefs GHSA-6q8p-8fxx-wc7f\n\nCo-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>",
"is_bot": true,
"headline": "fix: validate caller-supplied websiteUrl before embedding in HTML",
"author_name": "dobby-yivi-agent[bot]",
"author_login": "dobby-coder[bot]",
"committed_at": "2026-07-02T03:02:30Z",
"body_truncated": false,
"is_coding_agent": true
},
{
"oid": "b7675779bf00bfcf5d821086dec2671948946ebe",
"body": "Resolve all open dependency CVEs (undici, vite) flagged in #91.\n\n- undici bumped to 7.28.0 (top-level) and 6.27.0 (under\n @actions/http-client), fixing GHSA-vmh5-mc38-953g, GHSA-vxpw-j846-p89q,\n GHSA-hm92-r4w5-c3mj, GHSA-p88m-4jfj-68fv, GHSA-pr7r-676h-xcf6,\n GHSA-35p6-xmwp-9g52, GHSA-g8m3-5g58-fq\n[…]\nm\n package tarball's bundled deps).\n\nnpm audit now reports 0 vulnerabilities. typecheck, build and the full\nvitest suite (162 tests) all pass.\n\nCo-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>",
"is_bot": true,
"headline": "chore: update dependencies to resolve undici and vite CVEs",
"author_name": "dobby-yivi-agent[bot]",
"author_login": "dobby-coder[bot]",
"committed_at": "2026-07-01T22:12:26Z",
"body_truncated": true,
"is_coding_agent": true
},
{
"oid": "f4debe00e35f7d8fe020619d04e4fbe9a3b94397",
"body": "feat: report client version on every PKG and Cryptify request",
"is_bot": false,
"headline": "Merge pull request #90 from encryption4all/feat/client-version-header",
"author_name": "Ruben Hensen",
"author_login": "rubenhensen",
"committed_at": "2026-06-19T12:08:27Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "a11c139c2595e4c861f0afae40c2bdccb3b1cd9e",
"body": "…build\n\nAddresses the two non-blocking review nits on #90:\n\n- detectHost() now recognises a Web Worker (WorkerGlobalScope/importScripts)\n as host=browser. WASM crypto is commonly offloaded to a worker where\n window/document are undefined, so that traffic was reported as unknown.\n detectHost is no\n[…]\ntime `prepare` lifecycle\n already regenerates version.ts from the bumped package.json and rebuilds,\n so the exec build just doubled the work.\n\nCo-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>",
"is_bot": true,
"headline": "fix: attribute Web Worker traffic as browser; drop redundant release …",
"author_name": "dobby-yivi-agent[bot]",
"author_login": "dobby-coder[bot]",
"committed_at": "2026-06-19T11:56:11Z",
"body_truncated": true,
"is_coding_agent": true
},
{
"oid": "261459cf59853ad34dffe4d4470dde730ace528e",
"body": "The two new tests hard-coded host='node', which fails on the Deno/Bun\nintegration lanes where detectHost() correctly returns 'deno'/'bun'. Mirror\ndetectHost()'s ordering in the runtime assertion, and test comma-sanitisation\ndirectly via an exported sanitizeField helper instead of stubbing runtime\nglobals (which is order-sensitive under Deno). Implementation unchanged.",
"is_bot": false,
"headline": "test: make client-version tests runtime-agnostic",
"author_name": "Ruben Hensen",
"author_login": "rubenhensen",
"committed_at": "2026-06-15T15:32:44Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "7dfbe518b3f3074b7e90d3bd287ce3e987bb10b2",
"body": "The SDK now stamps a 'host,host_version,pg-js,<version>' client-version header\n(reused from pg-pkg) onto every PKG and Cryptify request, so servers can attribute\ntraffic by SDK + version. A caller-supplied header (any casing) wins, so embedding\nhosts (e.g. the Outlook add-in sending pg4ol) keep thei\n[…]\n PostGuardBase (caller wins, case-insensitive).\n- Thread config.headers through the cryptify upload/download functions, which\n previously hard-coded their headers and dropped config.headers entirely.",
"is_bot": false,
"headline": "feat: send X-POSTGUARD-CLIENT-VERSION on every request",
"author_name": "Ruben Hensen",
"author_login": "rubenhensen",
"committed_at": "2026-06-15T15:06:47Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "70d435c2200d067caa73503e2c9a7028701bfc5b",
"body": "…-identity\n\nfix(decrypt): surface verified private signing identity to recipients",
"is_bot": false,
"headline": "Merge pull request #89 from encryption4all/fix/expose-private-signing…",
"author_name": "Ruben Hensen",
"author_login": "rubenhensen",
"committed_at": "2026-06-08T12:47:53Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "0f08840b08ccacf42d13ed70e998cabacc217050",
"body": "Drop the rationale comment in front of the unseal-result capture (the PR\ndescription already carries the why), and give the unsealAndCollect test\ndistinguishing public attributes so it actually locks in that the\npost-unseal VerificationResult overrides the pre-unseal sender.",
"is_bot": false,
"headline": "fix(decrypt): address PR feedback on verified-identity capture",
"author_name": "Ruben Hensen",
"author_login": "rubenhensen",
"committed_at": "2026-06-05T15:28:31Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "d76b07a301012ea6f596e8da2564f74a3fbf304f",
"body": "`unsealAndCollect` was discarding the value returned by\n`unsealer.unseal(...)`. pg-wasm's `StreamUnsealer.unseal` resolves with\nthe full `VerificationResult { public, private? }` derived from the\ninner IBS signature after decryption, so dropping it threw away every\nattribute the sender signed under \n[…]\neds to\nchange.\n\nA unit test for `unsealAndCollect` against a stub unsealer locks the\nbehavior in: it resolves `unseal()` with `{public, private}` and\nasserts the returned `sender.private.con` matches.",
"is_bot": false,
"headline": "fix(decrypt): surface verified private signing identity to recipients",
"author_name": "Ruben Hensen",
"author_login": "rubenhensen",
"committed_at": "2026-06-05T13:10:47Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "44e52504c003f77de9ad251855598293ec099621",
"body": "…n-test\n\nfix(upload): pin onUploadInit acceptance with a regression test",
"is_bot": false,
"headline": "Merge pull request #88 from encryption4all/fix/onuploadinit-regressio…",
"author_name": "Ruben Hensen",
"author_login": "rubenhensen",
"committed_at": "2026-06-03T09:48:30Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "a97dd9f7558f364aed2ef2b451bda5da3cd4f100",
"body": "A prior runtime validator hand-maintained an allowlist of upload keys\nand started rejecting `onUploadInit` when the type was extended but\nthe allowlist wasn't. The validator was removed in #87, but that\nlanded under a `refactor:` prefix so semantic-release didn't cut a\nrelease and consumers on @e4a/\n[…]\nat asserts\n`sealed.upload({ onUploadInit })` does not throw \"unknown option\" —\npinning the contract so any future attempt to reintroduce a\nhand-maintained allowlist can't silently re-break the option.",
"is_bot": false,
"headline": "fix(upload): pin onUploadInit acceptance with a regression test",
"author_name": "Ruben Hensen",
"author_login": "rubenhensen",
"committed_at": "2026-06-03T09:32:40Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "10261087db988e8190365e7683159cc496978e0f",
"body": "…ts-onUploadInit\n\nrefactor(upload): remove runtime option validator, trust the types",
"is_bot": false,
"headline": "Merge pull request #87 from encryption4all/fix/upload-validator-accep…",
"author_name": "Ruben Hensen",
"author_login": "rubenhensen",
"committed_at": "2026-06-03T09:25:10Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "f24762cdf850018990b33ebf752afbb096f9dd62",
"body": "`validateUploadOptions` duplicated the shape of `UploadOptions` as a\nhand-maintained set of allowed keys + per-key value-type checks. The\ntype already encodes all of that — and when we recently added\n`onUploadInit` to `UploadOptions` we updated the type but forgot the\nvalidator, so callers passing t\n[…]\nfriendly compile-time error, and untyped JS callers\nwill get a downstream failure that's no worse than what most JS\nlibraries provide. No public API change; the deleted code path was\npurely defensive.",
"is_bot": false,
"headline": "refactor(upload): remove runtime option validator, trust the types",
"author_name": "Ruben Hensen",
"author_login": "rubenhensen",
"committed_at": "2026-06-03T09:24:21Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "d3a716870397fa1fc1bce427223c51ec10ab907d",
"body": "…autounzip\n\nfeat(decrypt): onDownloadProgress callback + auto-unzip files",
"is_bot": false,
"headline": "Merge pull request #86 from encryption4all/feat/decrypt-progress-and-…",
"author_name": "Ruben Hensen",
"author_login": "rubenhensen",
"committed_at": "2026-06-02T10:51:32Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "c99027abd3f0aae9455455829c1f496bc545ba64",
"body": "…sanitize, concurrency, tests\n\n- ProgressPipe now buffers the latest state so a callback attached\n after bytes have flowed still receives one event. Fixes small-\n payload case where inspect() drains the whole stream before\n decrypt() can attach onDownloadProgress.\n- Restore DecryptFileResult.blob\n[…]\n-entry,\n directory-filter, empty, and ordering-under-concurrency cases\n- api.test.ts: Content-Length parsing — numeric, 0, non-numeric, missing\n- download.test.ts: sanitizeDownloadFilename edge cases",
"is_bot": false,
"headline": "fix(decrypt): address review — small-payload progress, escape hatch, …",
"author_name": "Ruben Hensen",
"author_login": "rubenhensen",
"committed_at": "2026-06-02T09:51:33Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "7fb2083905e08f7fc91bbb78021be0e0b28d7af0",
"body": "Lets consumers reference the package via `github:org/repo#branch` URLs\n(needed for cross-repo PRs where the website branch wants to test\nagainst an unpublished pg-js branch). npm installs devDependencies and\nruns prepare when fetching a git dep, building dist/ before the\npackage is wired into the consumer.",
"is_bot": false,
"headline": "chore: add prepare script so git-installs build automatically",
"author_name": "Ruben Hensen",
"author_login": "rubenhensen",
"committed_at": "2026-06-02T09:31:56Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "a4a4eeb4b9fa15316f4249fccdd1fd98087f2c8d",
"body": "Adds an onDownloadProgress callback to DecryptInput so consumers can\nrender a real progress bar during decrypt(). The download stream is\ncreated in inspect() but bytes flow during decrypt(); a mutable-\ncallback ProgressPipe bridges this gap. Total size comes from\nContent-Length; when absent the call\n[…]\nntry shortcut (raw-data round trip) is\npreserved.\n\nBREAKING CHANGE: DecryptFileResult.files is now Array<{name, blob}>\n(was string[]); DecryptFileResult.blob is removed; download() takes\nno arguments.",
"is_bot": false,
"headline": "feat(decrypt): add onDownloadProgress + auto-unzip files",
"author_name": "Ruben Hensen",
"author_login": "rubenhensen",
"committed_at": "2026-06-02T09:25:21Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "df02f3021a084d950e4c5050826c2c0a2c494020",
"body": "ci: enable npm cache in setup-node to speed up Integration / Delivery",
"is_bot": false,
"headline": "Merge pull request #85 from encryption4all/ci/npm-cache-setup-node",
"author_name": "Ruben Hensen",
"author_login": "rubenhensen",
"committed_at": "2026-06-02T08:20:21Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "b3a5225d5dbf4bc0e2ea7d0b4a8f83df565c89ca",
"body": "Add cache: 'npm' to all actions/setup-node@v6 steps in integration.yml\nand delivery.yml so npm ci benefits from package-lock.json-keyed caching.",
"is_bot": false,
"headline": "ci: enable npm cache in setup-node for faster installs",
"author_name": "Ruben Hensen",
"author_login": "rubenhensen",
"committed_at": "2026-06-02T07:52:02Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "1e16f117d5077948fc64612747e7569461b3922a",
"body": "…utes\n\nfeat(sign): accept Yivi condiscon in pg.sign.yivi attributes",
"is_bot": false,
"headline": "Merge pull request #78 from encryption4all/feat/condiscon-sign-attrib…",
"author_name": "Ruben Hensen",
"author_login": "rubenhensen",
"committed_at": "2026-06-02T07:45:43Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "f532af8c43d44d85d5f1d8843f524e49df6ee9e4",
"body": "- Add docstring to AttrConItem hinting at Array.isArray narrowing\n- Document that callers must not re-request the email attribute\n- Add test for empty-inner-array optional-discon convention",
"is_bot": false,
"headline": "fix: address dobby review comments",
"author_name": "Ruben Hensen",
"author_login": "rubenhensen",
"committed_at": "2026-06-02T07:40:15Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "a1dda70177a039c31509fb1719e9c21d9f77a52b",
"body": "…cause\n\nfix: preserve original error as cause in unsealAndCollect",
"is_bot": false,
"headline": "Merge pull request #84 from encryption4all/fix/preserve-unseal-error-…",
"author_name": "Ruben Hensen",
"author_login": "rubenhensen",
"committed_at": "2026-06-02T07:35:40Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "5e7059e41c7866c19c5effc8c4d3ab08d98adcb3",
"body": "perf: collect sealRaw output as chunks, single-allocate at end",
"is_bot": false,
"headline": "Merge pull request #83 from encryption4all/perf/sealraw-chunk-collect",
"author_name": "Ruben Hensen",
"author_login": "rubenhensen",
"committed_at": "2026-06-02T07:34:35Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "291bf601e6319364918e7d1c914599d112e0f65d",
"body": "…error\n\nfix(envelope): surface tier-3 upload failures instead of broken fallback",
"is_bot": false,
"headline": "Merge pull request #82 from encryption4all/fix/envelope-tier3-upload-…",
"author_name": "Ruben Hensen",
"author_login": "rubenhensen",
"committed_at": "2026-06-02T07:33:23Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "ce41f9b107d7545c5026040a999e1058381e044b",
"body": "unsealAndCollect swallowed every error from `unsealer.unseal()` and\nre-threw `IdentityMismatchError`, masking transient failures, aborts,\nand WASM bugs. Forward `ErrorOptions` through `DecryptionError` and\n`IdentityMismatchError` so the original error is reachable via\n`.cause`, and let `AbortError` propagate unchanged.\n\nCloses #80",
"is_bot": true,
"headline": "fix: preserve original error as cause in unsealAndCollect",
"author_name": "dobby-yivi-agent[bot]",
"author_login": "dobby-coder[bot]",
"committed_at": "2026-06-01T23:39:14Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "69b270e40bf94b98b4c598735b953856932c4bcf",
"body": "sealRaw was reallocating and copying the entire prior buffer on every\nwrite — O(N²·c) bytes copied for N chunks of average size c. Mirror the\ndecrypt.ts:218-224 pattern: collect chunks during streaming, then\nallocate once and copy each into place. Output bytes unchanged.\n\nCloses #81",
"is_bot": true,
"headline": "perf: collect sealRaw output as chunks, single-allocate at end",
"author_name": "dobby-yivi-agent[bot]",
"author_login": "dobby-coder[bot]",
"committed_at": "2026-06-01T23:38:35Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "d728838eead306050f2d1f94af6eb86deef66928",
"body": "…broken fallback\n\nTier 3 has no local attachment, so swallowing a Cryptify upload rejection produced an envelope whose manual-upload body pointed the recipient at a file that does not exist. Re-throw the error on tier 3; tier 2 still falls through to manual-upload (attachment is the fallback) but now warns instead of being fully silent.\n\nCloses #79",
"is_bot": true,
"headline": "fix(envelope): surface tier-3 upload failures instead of producing a …",
"author_name": "dobby-yivi-agent[bot]",
"author_login": "dobby-coder[bot]",
"committed_at": "2026-06-01T23:38:26Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "fbe1dec13061fc62faef54f2fb0c592684e5c615",
"body": "Extend `YiviSign.attributes` from `AttrReq[]` to `AttrConItem[]`, where\neach entry is either a single attribute (legacy flat shape — keep\n`optional: true` for the skip-one case) or a Yivi\ndisjunction-of-conjunctions `AttrReq[][]`. An empty inner array marks\nthe discon as optional, mirroring Yivi nat\n[…]\nr, additive bump.\n\nTests: new `buildStartRequestBody` block in `tests/postguard.test.ts`\ncovers the no-attr, email-bound, legacy-flat-with-optional, and\nmixed-Single/Discon cases. `npm test`: 115/115.",
"is_bot": false,
"headline": "feat(sign): accept Yivi condiscon in pg.sign.yivi attributes",
"author_name": "Ruben Hensen",
"author_login": "rubenhensen",
"committed_at": "2026-06-01T13:40:32Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "e11942da25a6470c76be42f044566deca5a916ff",
"body": "…-notice\n\nfeat(upload): info notice when notify is unset on first upload",
"is_bot": false,
"headline": "Merge pull request #77 from encryption4all/feat/upload-silent-default…",
"author_name": "Ruben Hensen",
"author_login": "rubenhensen",
"committed_at": "2026-05-24T13:44:10Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "61b4459270d5ca36a553358213bf5edcf7301502",
"body": "Closes the one failure mode neither TypeScript nor validateUploadOptions\ncan catch: a caller who simply forgets to set `notify` and silently gets\nno recipient email. `notify` is optional in `UploadOptions`, so leaving\nit out type-checks cleanly — the user only finds out via a support\nticket that \"th\n[…]\nt) so long-running\nprocesses don't flood. Suppressed by passing `notify` explicitly:\n`{ notify: { recipients: true } }` to email, or\n`{ notify: { recipients: false } }` to keep silent intent explicit.",
"is_bot": false,
"headline": "feat(upload): info notice when notify is unset on first upload",
"author_name": "Ruben Hensen",
"author_login": "rubenhensen",
"committed_at": "2026-05-24T13:42:45Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "b6d972601de45fd51a5094f39819fe788e992e70",
"body": "…upport\n\nfeat(runtime): support Node 20.3+, Bun, and Deno for encrypt + upload",
"is_bot": false,
"headline": "Merge pull request #76 from encryption4all/feat/non-browser-runtime-s…",
"author_name": "Ruben Hensen",
"author_login": "rubenhensen",
"committed_at": "2026-05-24T13:32:45Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "c3f3e78697455c6ba6abf69704a506fe1af189c0",
"body": "tsdown@0.22 requires Node ^22.18 || >=24; it can't build on Node 20.\nSince we don't intend to claim Node 20 support, drop it from the matrix\nand bump engines.node to >=22 to match. README and CLAUDE.md updated to\nreflect Node 22 as the floor.",
"is_bot": false,
"headline": "ci: drop Node 20 from matrix, require Node 22+",
"author_name": "Ruben Hensen",
"author_login": "rubenhensen",
"committed_at": "2026-05-24T13:23:53Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "0f8b2080bf71cfffc65ce35e63eb37e8182f62b6",
"body": "Addresses inline comments from dobby-coder's review:\n\n- src/util/zip.ts: narrow the conflux self shim to save/restore around\n the dynamic import instead of permanently mutating globalThis.self.\n Avoids surprising downstream libraries that use `typeof self !==\n 'undefined'` as a browser-detection \n[…]\nns surface here as a CI failure\n on this SDK rather than confused for upstream breakage.\n\n- README.md: documents the minimum Bun (1.0.16+) and Deno (1.39+)\n versions — both gated by AbortSignal.any.",
"is_bot": false,
"headline": "refactor(runtime): apply review feedback from #76",
"author_name": "Ruben Hensen",
"author_login": "rubenhensen",
"committed_at": "2026-05-24T13:04:49Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "107870acfef70baf59ea8647b087f5e028cb2526",
"body": "README gets a \"Server-side usage\" section listing what works (encrypt +\nupload via sign.apiKey or sign.session), what's browser-only (sign.yivi,\nresult.download), and how to run scripts/smoke.mjs.\n\nCLAUDE.md gets a runtime-support section plus notes on the two\nnon-obvious gotchas (FileList guard, conflux self shim) so future\ncontributors don't accidentally regress them.",
"is_bot": false,
"headline": "docs: document Node, Bun, and Deno runtime support",
"author_name": "Ruben Hensen",
"author_login": "rubenhensen",
"committed_at": "2026-05-24T12:46:22Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "c2cbf0c9239ffbe35d26639710de546ec694ca87",
"body": "Adds three CI jobs to integration.yml: Node (matrix over 20/22/24), Bun,\nand Deno. Each lane builds the dist and runs scripts/smoke.mjs in dry\nmode, which loads the dist, exercises createZipReadable, and verifies\nglobal availability — catches both bugs fixed in the previous commit\nwithout needing live credentials.\n\nThe smoke script can also do a full live upload when PG_API_KEY is set;\nuseful for one-off manual verification.",
"is_bot": false,
"headline": "ci: matrix-test Node 20/22/24, Bun, and Deno with smoke script",
"author_name": "Ruben Hensen",
"author_login": "rubenhensen",
"committed_at": "2026-05-24T12:46:22Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "2adff7ae11d0de16f6d2367518791f3032ce6ad0",
"body": "Two bugs prevented the SDK from working in non-browser runtimes:\n\n 1. `instanceof FileList` in sealed.ts throws ReferenceError when\n FileList is not a global. FileList is browser-only; Node, Bun, and\n Deno don't have it. Now typeof-guarded.\n\n 2. `@transcend-io/conflux/dist/esm/bigint.js` r\n[…]\nlower bound is required\nfor AbortSignal.any, which the encrypt pipeline uses.\n\nVerified end-to-end against staging.postguard.eu under Node 26, Bun\n1.3.14, and Deno 2.8.0 (real UUID returned for each).",
"is_bot": false,
"headline": "feat(runtime): support Node 20.3+, Bun, and Deno for encrypt + upload",
"author_name": "Ruben Hensen",
"author_login": "rubenhensen",
"committed_at": "2026-05-24T12:46:22Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "89b07223ca5600097020ee9356f468e6ea95419a",
"body": "Captures the prebuild generator step (base64 WASM + wasm-bindgen shim\npatch), the lazy builder surface, and the Conventional Commits\nrequirement so future contributors don't have to reverse-engineer them.",
"is_bot": false,
"headline": "docs: add CLAUDE.md with build, architecture, and pg-wasm patching notes",
"author_name": "Ruben Hensen",
"author_login": "rubenhensen",
"committed_at": "2026-05-24T12:44:23Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "3b9c2f20ebe01b9fc436aaad9e709ba8cc6c356b",
"body": "Surfaces the most common misconfigurations (boolean notify, top-level\nrecipients, typos in notify keys, non-object opts) as synchronous\nTypeErrors with messages that point at the correct shape. Previously\nthese silently degraded to \"no notification email sent\" because every\nfield coalesced to undefined and Cryptify defaulted to silent.",
"is_bot": false,
"headline": "feat(upload): validate upload options shape with clear errors",
"author_name": "Ruben Hensen",
"author_login": "rubenhensen",
"committed_at": "2026-05-24T12:43:32Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "3af134536d127d14ad1866c33e81b44b68feae11",
"body": "chore: bump vitest to 4.1.7",
"is_bot": false,
"headline": "Merge pull request #75 from encryption4all/chore/vitest-4.1.7",
"author_name": "Ruben Hensen",
"author_login": "rubenhensen",
"committed_at": "2026-05-21T11:55:34Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "2dabb9351eac9577a43bb89d7af8ef1a5514397a",
"body": null,
"is_bot": true,
"headline": "chore: bump vitest to 4.1.7",
"author_name": "dobby-yivi-agent[bot]",
"author_login": "dobby-coder[bot]",
"committed_at": "2026-05-20T22:20:35Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "39a877af5275796e53390fb56131050c6bd47629",
"body": "chore: update dependencies",
"is_bot": false,
"headline": "Merge pull request #73 from encryption4all/chore/update-deps-2026-05-16",
"author_name": "Ruben Hensen",
"author_login": "rubenhensen",
"committed_at": "2026-05-17T15:59:42Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "c6905cfa83e24601b15ad725ab5a9e0d917e0af2",
"body": "- @e4a/pg-wasm 0.6.0 -> 0.6.1\n- @privacybydesign/yivi-client 1.0.0-beta.6 -> 1.0.0\n- @privacybydesign/yivi-core 1.0.0-beta.6 -> 1.0.0\n- @privacybydesign/yivi-css 1.0.0-beta.7 -> 1.0.1\n- @privacybydesign/yivi-web 1.0.0-beta.6 -> 1.0.1\n\nRefs #72",
"is_bot": true,
"headline": "chore: update dependencies",
"author_name": "dobby-yivi-agent[bot]",
"author_login": "dobby-coder[bot]",
"committed_at": "2026-05-16T22:18:47Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "dcdd6591f58976364f8220f4cedbe86d0e2bce3b",
"body": "feat(upload): onUploadInit callback exposes uuid + recoveryToken pre-chunk",
"is_bot": false,
"headline": "Merge pull request #71 from encryption4all/feat/upload-init-callback",
"author_name": "Ruben Hensen",
"author_login": "rubenhensen",
"committed_at": "2026-05-16T12:48:18Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "b8a483ab5d9dc72b20eec89eab6dc8c8da5032af",
"body": "…chunk\n\nAdds `onUploadInit?: (info: { uuid: string; recoveryToken: string }) => void`\nto `UploadOptions` and `CreateEnvelopeOptions`. Fires once, synchronously\nafter `upload_init` resolves and before the first chunk PUT, so callers\n(Outlook/Thunderbird addons) can persist `{uuid, recoveryToken}` to \n[…]\nam, and\nforwarded through createEnvelope.\n\nCloses #68\nRefs encryption4all/postguard-tb-addon#103\nRefs encryption4all/postguard-outlook-addon#82\n\nCo-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>",
"is_bot": true,
"headline": "feat(upload): onUploadInit callback exposes uuid + recoveryToken pre-…",
"author_name": "dobby-yivi-agent[bot]",
"author_login": "dobby-coder[bot]",
"committed_at": "2026-05-16T12:43:56Z",
"body_truncated": true,
"is_coding_agent": true
},
{
"oid": "9453662c37e327d1d854a77a12f9ab008ae54dde",
"body": "chore: bump @e4a/pg-wasm to 0.6.0",
"is_bot": false,
"headline": "Merge pull request #70 from encryption4all/chore/pg-wasm-0.6.0",
"author_name": "Ruben Hensen",
"author_login": "rubenhensen",
"committed_at": "2026-05-16T10:24:51Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "eb457cac640c13d4d33b2767cfcf951a8875badc",
"body": "Tracks postguard pg-core-v0.6.0. Public TypeScript API surface is unchanged; only internal wasm-export symbol order shifted and ReadableStreamReaderMode became exported.\n\nCloses #69",
"is_bot": true,
"headline": "chore: bump @e4a/pg-wasm to 0.6.0",
"author_name": "dobby-yivi-agent[bot]",
"author_login": "dobby-coder[bot]",
"committed_at": "2026-05-13T22:14:30Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "146a7ab70ea8acc6071a4c773a8ae467c1c391a9",
"body": "fix: unwrap data.bin from zip in Opened.decrypt uuid mode",
"is_bot": false,
"headline": "Merge pull request #67 from encryption4all/fix/data-bin-unwrap-39",
"author_name": "Ruben Hensen",
"author_login": "rubenhensen",
"committed_at": "2026-05-13T16:29:25Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "959a0c2526aa8cee5ee4273635885953d864aa59",
"body": "Restores symmetry with Sealed.upload() data: mode, which wraps raw bytes\nas a single-entry zip named data.bin. When Opened.decrypt() sees a uuid\nresult whose central directory is exactly ['data.bin'], it now returns\nDecryptDataResult { plaintext, sender } instead of forcing consumers to\nwalk the zip\n[…]\nven (conflux's streaming\nwriter leaves compressedSize=0 in the LFH, so LFH-only walking fails)\nand supports stored (method 0) and deflate (method 8) via\nDecompressionStream('deflate-raw').\n\nCloses #39",
"is_bot": true,
"headline": "fix: unwrap data.bin from zip in Opened.decrypt uuid mode",
"author_name": "dobby-yivi-agent[bot]",
"author_login": "dobby-coder[bot]",
"committed_at": "2026-05-13T16:22:04Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "afc9b71ad518127f8bc60bd7923cc8d6e6637c45",
"body": "chore: update vitest to 4.1.6",
"is_bot": false,
"headline": "Merge pull request #64 from encryption4all/chore/vitest-4.1.6",
"author_name": "Ruben Hensen",
"author_login": "rubenhensen",
"committed_at": "2026-05-13T15:53:59Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "6205fc309aaf954e82937beae723912812604f2e",
"body": "…y-token\n\nfeat(api): capture recovery_token and add resumeUpload for cross-restart resume",
"is_bot": false,
"headline": "Merge pull request #66 from encryption4all/feat/upload-resume-recover…",
"author_name": "Ruben Hensen",
"author_login": "rubenhensen",
"committed_at": "2026-05-13T15:42:25Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "3838012e3a1883ae295e3baef404def79b951caf",
"body": "…art resume\n\nWires the cross-refresh-resume protocol on the SDK side (cryptify#148):\n\n- initUpload now reads the snake-case recovery_token field from the JSON\n body and returns it on FileState as recoveryToken.\n- New resumeUpload(cryptifyUrl, uuid, recoveryToken) calls\n GET /fileupload/{uuid}/stat\n[…]\ntoken cases per cryptify's\n info-hiding behaviour).\n- FileState and resumeUpload re-exported from the package root so\n consumers (Office addon, Thunderbird, website) can own persistence.\n\nCloses #65",
"is_bot": true,
"headline": "feat(api): capture recovery_token and add resumeUpload for cross-rest…",
"author_name": "dobby-yivi-agent[bot]",
"author_login": "dobby-coder[bot]",
"committed_at": "2026-05-13T14:55:29Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "e07f68e0b1870eaa7279e0d3fb3d636076db2b8b",
"body": "Patch bump from 4.1.5 → 4.1.6. Closes #63.",
"is_bot": true,
"headline": "chore: update vitest to 4.1.6",
"author_name": "dobby-yivi-agent[bot]",
"author_login": "dobby-coder[bot]",
"committed_at": "2026-05-12T22:17:59Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "4b78ab4dadc50e9df1578db6f8080abeba07eefd",
"body": "fix(decrypt-session): bound JWT cache and sweep expired entries on write",
"is_bot": false,
"headline": "Merge pull request #61 from encryption4all/fix/jwt-cache-bound-sweep",
"author_name": "Ruben Hensen",
"author_login": "rubenhensen",
"committed_at": "2026-05-12T07:23:12Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "a8a9a25ddbad8f76ab04f65ee453b5730ff6a9df",
"body": "perf(envelope): skip base64 of full ciphertext for tier 2/3",
"is_bot": false,
"headline": "Merge pull request #62 from encryption4all/fix/skip-base64-tier-2-3",
"author_name": "Ruben Hensen",
"author_login": "rubenhensen",
"committed_at": "2026-05-12T07:20:39Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "53a6db6c42c22c0537149750cf27bb7453ed4544",
"body": "Tier 2/3 envelopes never embed base64 in the body — they use Cryptify\nupload URLs. Computing the full base64 string just to feed pickTier\nwasted CPU and a ciphertext-sized intermediate String (~33% larger\nthan the bytes themselves) that was immediately discarded.\n\nDerive the base64 length arithmetic\n[…]\nd only call uint8ArrayToBase64 inside the tier-1 branch.\nAlso replaces the char-by-char btoa loop with a chunked\nString.fromCharCode(...subarray) accumulator for the remaining tier-1\npath.\n\nCloses #58",
"is_bot": true,
"headline": "perf(envelope): skip base64 of full ciphertext for tier 2/3",
"author_name": "dobby-yivi-agent[bot]",
"author_login": "dobby-coder[bot]",
"committed_at": "2026-05-11T23:39:06Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "f5986dd19a25f5bd674ed046ae91695549c9ae97",
"body": "Cap jwtCache at 100 entries with LRU eviction so long-lived SPA sessions\nthat decrypt for many recipients can't grow the Map indefinitely. On every\ncacheJwt write, sweep entries past their exp (with the same 30s margin used\non read) so expired-but-unread keys don't linger.\n\nCloses #59",
"is_bot": true,
"headline": "fix(decrypt-session): bound JWT cache and sweep expired entries on write",
"author_name": "dobby-yivi-agent[bot]",
"author_login": "dobby-coder[bot]",
"committed_at": "2026-05-11T23:39:00Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "cd3b05d16e5964cb2681d28e30637e68d243ba8a",
"body": "chore: bump tsdown to 0.22.0 and yivi-css to 1.0.0-beta.7",
"is_bot": false,
"headline": "Merge pull request #57 from encryption4all/chore/bump-deps-2026-05-10",
"author_name": "Ruben Hensen",
"author_login": "rubenhensen",
"committed_at": "2026-05-10T22:37:42Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "2e6ad1abf96d19154448c0ae494e5fd87985b8a5",
"body": null,
"is_bot": true,
"headline": "chore: bump tsdown to 0.22.0 and yivi-css to 1.0.0-beta.7",
"author_name": "dobby-yivi-agent[bot]",
"author_login": "dobby-coder[bot]",
"committed_at": "2026-05-10T22:19:15Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "a60716e0b4eaaed0f3763a2eebbcf6c39fc0560d",
"body": "feat: release with @privacybydesign/yivi-* 1.0.0-beta.6",
"is_bot": false,
"headline": "Merge pull request #54 from encryption4all/chore/release-yivi-beta.6",
"author_name": "Ruben Hensen",
"author_login": "rubenhensen",
"committed_at": "2026-05-07T20:44:01Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "d282facf84bb1d1669c7c38f3bee9deda06317d8",
"body": null,
"is_bot": false,
"headline": "feat: release with @privacybydesign/yivi-* 1.0.0-beta.6",
"author_name": "Ruben Hensen",
"author_login": "rubenhensen",
"committed_at": "2026-05-07T20:43:32Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "837382d71df3438efb7c42b78ec5b7065bc70f68",
"body": "chore: bump @privacybydesign/yivi-* to 1.0.0-beta.6",
"is_bot": false,
"headline": "Merge pull request #53 from encryption4all/chore/yivi-1.0.0-beta.6",
"author_name": "Ruben Hensen",
"author_login": "rubenhensen",
"committed_at": "2026-05-07T20:41:18Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "1ace739a67a87f547369e926b66adc4313d0c5e7",
"body": null,
"is_bot": false,
"headline": "chore: bump @privacybydesign/yivi-* to 1.0.0-beta.6",
"author_name": "Ruben Hensen",
"author_login": "rubenhensen",
"committed_at": "2026-05-07T20:40:29Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "95af57cc8bfb42d42baf9ac04c5293c4b9b1abaa",
"body": "feat(download): resume via Range header on transient stream failures",
"is_bot": false,
"headline": "Merge pull request #52 from encryption4all/feat/resumable-downloads",
"author_name": "Ruben Hensen",
"author_login": "rubenhensen",
"committed_at": "2026-05-07T19:00:38Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "e9f60c44b27f10b035905e221a188aed53332ea5",
"body": "Cryptify's `FileServer` already supports HTTP Range, so a stream-level\nfailure mid-download (network drop, idle timeout) can now resume from\nthe byte offset reached rather than restarting from zero. The consumer\nsees a single contiguous stream regardless of how many internal retries\nhappened.\n\nImple\n[…]\nqueued\nchunks, so the test-only `streamThenError` is now pull-based to\nguarantee chunks are delivered before the error fires.\n\n72 tests pass; type-check clean.\n\nCloses #48.\nRefs #117, #136, #145, #47.",
"is_bot": false,
"headline": "feat(download): resume via Range header on transient stream failures",
"author_name": "Ruben Hensen",
"author_login": "rubenhensen",
"committed_at": "2026-05-07T18:44:45Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "e55302aab22e429917fb57704331af80da233026",
"body": "ci: add semantic PR title check workflow",
"is_bot": false,
"headline": "Merge pull request #51 from encryption4all/ci/semantic-pr-title",
"author_name": "Ruben Hensen",
"author_login": "rubenhensen",
"committed_at": "2026-05-07T18:38:06Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "629fff28fd173b4737601bd8dd755cfd93f8f73b",
"body": "Closes #49",
"is_bot": false,
"headline": "ci: add semantic PR title check workflow",
"author_name": "Ruben Hensen",
"author_login": "rubenhensen",
"committed_at": "2026-05-07T18:36:49Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "3b0f0dd871b7bdd25e652ce75b6c74da010e7e59",
"body": "chore(deps): bump @privacybydesign/yivi-* to 1.0.0-beta.5",
"is_bot": false,
"headline": "Merge pull request #50 from encryption4all/chore/yivi-beta-5",
"author_name": "Ruben Hensen",
"author_login": "rubenhensen",
"committed_at": "2026-05-07T18:18:26Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "9d4f49e7d71b49311f4541c6cc3edc1bc3f46b17",
"body": null,
"is_bot": false,
"headline": "chore(deps): bump @privacybydesign/yivi-* to 1.0.0-beta.5",
"author_name": "Ruben Hensen",
"author_login": "rubenhensen",
"committed_at": "2026-05-07T18:14:15Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "3f52a9a7eb25ebf0c7b6555a0f7897219fd7b41d",
"body": "feat(upload): retry chunks and downloads with exponential backoff",
"is_bot": false,
"headline": "Merge pull request #47 from encryption4all/feat/upload-retry-backoff",
"author_name": "Ruben Hensen",
"author_login": "rubenhensen",
"committed_at": "2026-05-07T12:11:39Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "4081e1bdf44e0dd8d27783a85dce0ea3c9058bdc",
"body": "Three small follow-ups from @dobby-coder's review:\n\n1. Flatten `throwSessionExpiredOrNetworkError` — replace the\n throw-inside-try / catch-and-rethrow pattern with a flatter\n parse-then-decide shape. Same behaviour, no `instanceof` re-check\n needed in the catch.\n\n2. Document `downloadTimeoutMs\n[…]\nIDE go-to-definition. No runtime change.\n\n`AbortSignal.any` is left as-is — it's already used in\n`src/crypto/encrypt.ts:45`, so this PR doesn't introduce a new\nbrowser-support floor.\n\nRefs #47 review.",
"is_bot": false,
"headline": "refactor(retry): apply review feedback from #47",
"author_name": "Ruben Hensen",
"author_login": "rubenhensen",
"committed_at": "2026-05-07T11:46:54Z",
"body_truncated": true,
"is_coding_agent": false
}
],
"releases_count": 49,
"commits_last_year": 210,
"latest_release_at": "2026-07-17T05:57:23Z",
"latest_release_tag": "v2.3.1",
"releases_from_tags": false,
"days_since_last_push": 0,
"active_weeks_last_year": 16,
"days_since_latest_release": 4,
"mean_days_between_releases": 1.6
},
"community": {
"has_readme": true,
"has_license": false,
"has_description": false,
"has_contributing": false,
"health_percentage": 25,
"has_issue_template": false,
"has_code_of_conduct": false,
"has_pull_request_template": false
},
"ecosystem": {
"packages": [
{
"name": "@e4a/pg-js",
"exists": true,
"license": "MIT",
"keywords": [
"postguard",
"ibe",
"encryption",
"identity-based-encryption",
"yivi",
"irma"
],
"ecosystem": "npm",
"matches_repo": true,
"registry_url": "https://www.npmjs.com/package/@e4a/pg-js",
"is_deprecated": false,
"latest_version": "2.3.1",
"repository_url": "https://github.com/encryption4all/postguard-js",
"versions_count": 51,
"total_downloads": null,
"dependents_count": null,
"deprecation_note": null,
"maintainers_count": 3,
"monthly_downloads": 4063,
"first_published_at": "2026-03-25T14:29:26.192000Z",
"latest_published_at": "2026-07-17T05:57:22.376000Z",
"latest_version_yanked": null,
"days_since_latest_publish": 4
}
]
},
"popularity": {
"forks": 0,
"stars": 0,
"watchers": 0,
"fork_history": {
"days": [],
"complete": true,
"collected": 0,
"total_forks": 0
},
"star_history": {
"days": [],
"complete": true,
"collected": 0,
"total_stars": 0
},
"open_issues_and_prs": 6
},
"ai_readiness": {
"has_nix": false,
"example_dirs": [],
"has_llms_txt": false,
"has_dockerfile": false,
"has_mcp_signal": false,
"bootstrap_files": [],
"api_schema_files": [],
"has_devcontainer": false,
"typecheck_configs": [
"tsconfig.json"
],
"toolchain_manifests": [],
"largest_source_bytes": 41642,
"source_files_sampled": 60,
"oversized_source_files": 0,
"agent_instruction_files": [
"CLAUDE.md"
],
"agent_instruction_max_bytes": 10266
},
"dependencies": {
"manifests": [
"package.json"
],
"advisories": {
"error": null,
"scope": null,
"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": 0,
"direct_affected_count": 0
},
"ecosystems": [
"npm"
],
"dependencies": [
{
"name": "@e4a/pg-wasm",
"manifest": "package.json",
"ecosystem": "npm",
"version_constraint": "^0.6.1"
},
{
"name": "@privacybydesign/yivi-client",
"manifest": "package.json",
"ecosystem": "npm",
"version_constraint": "^1.0.0"
},
{
"name": "@privacybydesign/yivi-core",
"manifest": "package.json",
"ecosystem": "npm",
"version_constraint": "^1.0.0"
},
{
"name": "@privacybydesign/yivi-css",
"manifest": "package.json",
"ecosystem": "npm",
"version_constraint": "^1.0.1"
},
{
"name": "@privacybydesign/yivi-web",
"manifest": "package.json",
"ecosystem": "npm",
"version_constraint": "^1.0.1"
},
{
"name": "@transcend-io/conflux",
"manifest": "package.json",
"ecosystem": "npm",
"version_constraint": "^6.1.3"
}
],
"all_dependencies": {
"error": "GitHub dependency-graph SBOM unavailable (404); the dependency graph may be disabled for this repository",
"source": null,
"packages": [],
"collected": false,
"truncated": false,
"total_count": null,
"direct_count": null,
"indirect_count": null
}
},
"maintainership": {
"issues": {
"open_prs": 2,
"merged_prs": 75,
"open_issues": 4,
"closed_ratio": 0.897,
"closed_issues": 35,
"closed_unmerged_prs": 1
},
"bus_factor": 1,
"bot_contributors": 1,
"top_contributors": [
{
"type": "User",
"login": "rubenhensen",
"commits": 177,
"avatar_url": "https://avatars.githubusercontent.com/u/17710718?v=4"
}
],
"contributors_sampled": 1,
"top_contributor_share": 1
},
"quality_signals": {
"has_ci": true,
"has_tests": true,
"ci_workflows": [
"delivery.yml",
"integration.yml",
"pr-title.yml"
],
"has_docs_dir": false,
"linter_configs": [],
"has_editorconfig": false,
"has_linter_config": false,
"has_precommit_config": false
},
"security_signals": {
"lockfiles": [
"package-lock.json"
],
"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": 5,
"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": "14 out of 14 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": 0,
"reason": "no effort to earn an OpenSSF best practices badge detected",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#cii-best-practices"
},
{
"name": "Code-Review",
"score": 8,
"reason": "Found 4/5 approved changesets -- score normalized to 8",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#code-review"
},
{
"name": "Contributors",
"score": 0,
"reason": "project has 0 contributing companies or organizations -- score normalized to 0",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#contributors"
},
{
"name": "Dangerous-Workflow",
"score": 10,
"reason": "no dangerous workflow patterns detected",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#dangerous-workflow"
},
{
"name": "Dependency-Update-Tool",
"score": 0,
"reason": "no 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": 0,
"reason": "license file not detected",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#license"
},
{
"name": "Maintained",
"score": 10,
"reason": "30 commit(s) and 0 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": 10,
"reason": "packaging workflow detected",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#packaging"
},
{
"name": "Pinned-Dependencies",
"score": 5,
"reason": "dependency not pinned by hash detected -- score normalized to 5",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#pinned-dependencies"
},
{
"name": "SAST",
"score": 0,
"reason": "SAST tool is not run on all commits -- score normalized to 0",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#sast"
},
{
"name": "Security-Policy",
"score": 0,
"reason": "security policy file not detected",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#security-policy"
},
{
"name": "Signed-Releases",
"score": null,
"reason": "no releases found",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#signed-releases"
},
{
"name": "Token-Permissions",
"score": 0,
"reason": "detected GitHub workflow tokens with excessive permissions",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#token-permissions"
},
{
"name": "Vulnerabilities",
"score": 9,
"reason": "1 existing vulnerabilities detected",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#vulnerabilities"
}
],
"commit": "6c0f55443706ba27fda1db73f48655795cb151d9",
"ran_at": "2026-07-22T02:15:14Z",
"aggregate_score": 5.3,
"scorecard_version": "v5.5.0"
},
"has_codeql_workflow": false,
"has_security_policy": false,
"has_dependabot_config": false
},
"contribution_flow": {
"collected": true,
"ci_last_run_at": "2026-07-21T14:57:55Z",
"oldest_open_prs": [
{
"number": 112,
"created_at": "2026-07-14T23:40:01Z",
"last_comment_at": null,
"last_comment_author": null
},
{
"number": 114,
"created_at": "2026-07-14T23:41:39Z",
"last_comment_at": null,
"last_comment_author": null
}
],
"last_merged_pr_at": "2026-07-21T14:56:49Z",
"ci_last_conclusion": "SUCCESS",
"oldest_open_issues": [
{
"number": 94,
"created_at": "2026-07-01T23:13:54Z",
"last_comment_at": "2026-07-02T03:55:34Z",
"last_comment_author": "dobby-coder"
},
{
"number": 105,
"created_at": "2026-07-14T23:36:01Z",
"last_comment_at": "2026-07-14T23:37:02Z",
"last_comment_author": "dobby-coder"
},
{
"number": 107,
"created_at": "2026-07-14T23:36:23Z",
"last_comment_at": "2026-07-14T23:37:04Z",
"last_comment_author": "dobby-coder"
},
{
"number": 110,
"created_at": "2026-07-14T23:36:50Z",
"last_comment_at": null,
"last_comment_author": null
}
]
}
},
"config": {
"disabled_metrics": [],
"disabled_categories": [],
"disabled_components": {}
},
"source": {
"url": "https://github.com/encryption4all/postguard-js",
"host": "github.com",
"name": "postguard-js",
"owner": "encryption4all"
},
"metrics": {
"overall": {
"key": "overall",
"band": "moderate",
"name": "Overall health",
"note": null,
"notes": [],
"value": 57,
"inputs": {
"security": 53,
"vitality": 85,
"community": 24,
"governance": 60,
"engineering": 57
},
"components": []
},
"categories": [
{
"key": "vitality",
"band": "excellent",
"name": "Vitality",
"value": 85,
"weight": 0.22,
"metrics": [
{
"key": "development_activity",
"band": "good",
"name": "Development activity",
"note": null,
"notes": [],
"value": 75,
"inputs": {
"commits_last_year": 210,
"human_commit_share": 0.74,
"days_since_last_push": 0,
"active_weeks_last_year": 16
},
"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": "16/52 weeks with commits",
"points": 11.1,
"status": "partial",
"details": [
{
"code": "commit_cadence_weeks",
"params": {
"weeks": 16
}
}
],
"max_points": 36
},
{
"key": "commit_volume",
"name": "Commit volume",
"detail": "210 commits in the last year",
"points": 18,
"status": "met",
"details": [
{
"code": "commits_last_year",
"params": {
"count": 210
}
}
],
"max_points": 18
},
{
"key": "openssf_scorecard_maintained",
"name": "OpenSSF Scorecard: Maintained",
"detail": "30 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 10",
"points": 10,
"status": "met",
"details": [],
"max_points": 10
}
]
},
{
"key": "release_discipline",
"band": "excellent",
"name": "Release discipline",
"note": "Excluded from scoring (no data or not applicable): OpenSSF Scorecard: Signed-Releases. Remaining weights renormalized.",
"notes": [
{
"code": "excluded_no_data",
"params": {
"components": [
"openssf_scorecard_signed_releases"
]
}
},
{
"code": "weights_renormalized",
"params": {}
}
],
"value": 100,
"inputs": {
"releases_count": 49,
"latest_release_tag": "v2.3.1",
"releases_from_tags": false,
"days_since_latest_release": 4,
"mean_days_between_releases": 1.6
},
"components": [
{
"key": "ships_releases",
"name": "Ships releases",
"detail": "49 releases published",
"points": 27,
"status": "met",
"details": [
{
"code": "releases_published",
"params": {
"count": 49
}
}
],
"max_points": 27
},
{
"key": "release_recency",
"name": "Release recency",
"detail": "latest release 4 days ago",
"points": 36,
"status": "met",
"details": [
{
"code": "release_recency",
"params": {
"days": 4
}
}
],
"max_points": 36
},
{
"key": "release_cadence",
"name": "Release cadence",
"detail": "a release every ~1.6 days",
"points": 27,
"status": "met",
"details": [
{
"code": "release_cadence",
"params": {
"gap": 1.6
}
}
],
"max_points": 27
},
{
"key": "openssf_scorecard_signed_releases",
"name": "OpenSSF Scorecard: Signed-Releases",
"detail": "no releases found",
"points": 0,
"status": "excluded",
"details": [
{
"code": "no_data",
"params": {}
}
],
"max_points": 10
}
]
},
{
"key": "abandonment",
"band": "excellent",
"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": "critical",
"name": "Community & Adoption",
"value": 24,
"weight": 0.18,
"metrics": [
{
"key": "popularity",
"band": "critical",
"name": "Popularity & adoption",
"note": null,
"notes": [],
"value": 1,
"inputs": {
"forks": 0,
"stars": 0,
"watchers": 0,
"growth_state": "unverified",
"growth_factor_pct": 100,
"growth_unverified_reason": "no_history"
},
"components": [
{
"key": "stars",
"name": "Stars",
"detail": "0 stars",
"points": 0,
"status": "missed",
"details": [
{
"code": "stars",
"params": {
"count": 0
}
}
],
"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": "0 watchers",
"points": 0,
"status": "missed",
"details": [
{
"code": "watchers",
"params": {
"count": 0
}
}
],
"max_points": 15
}
]
},
{
"key": "community_health",
"band": "critical",
"name": "Community health",
"note": null,
"notes": [],
"value": 25,
"inputs": {
"has_readme": true,
"has_license": false,
"has_contributing": false,
"has_issue_template": false,
"has_code_of_conduct": false,
"has_pull_request_template": false
},
"components": [
{
"key": "readme",
"name": "README",
"detail": null,
"points": 22.5,
"status": "met",
"details": [],
"max_points": 22.5
},
{
"key": "license",
"name": "License",
"detail": "no license file detected",
"points": 0,
"status": "missed",
"details": [
{
"code": "license_absent",
"params": {}
}
],
"max_points": 22.5
},
{
"key": "contributing_guide",
"name": "CONTRIBUTING guide",
"detail": null,
"points": 0,
"status": "missed",
"details": [],
"max_points": 18
},
{
"key": "code_of_conduct",
"name": "Code of conduct",
"detail": null,
"points": 0,
"status": "missed",
"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": 0,
"status": "missed",
"details": [],
"max_points": 6.3
}
]
},
{
"key": "ecosystem_adoption",
"band": "moderate",
"name": "Ecosystem adoption (downloads)",
"note": "Excluded from scoring (no data or not applicable): Registry dependents. Remaining weights renormalized.",
"notes": [
{
"code": "excluded_no_data",
"params": {
"components": [
"registry_dependents"
]
}
},
{
"code": "weights_renormalized",
"params": {}
}
],
"value": 60,
"inputs": {
"packages": [
"@e4a/pg-js"
],
"dependents": null,
"ecosystems": "npm",
"total_downloads": null,
"monthly_downloads": 4063
},
"components": [
{
"key": "monthly_downloads",
"name": "Monthly downloads",
"detail": "4,063 downloads/month across npm",
"points": 48.1,
"status": "partial",
"details": [
{
"code": "downloads_monthly",
"params": {
"count": 4063,
"ecosystems": "npm"
}
}
],
"max_points": 80
},
{
"key": "registry_dependents",
"name": "Registry dependents",
"detail": "not reported by this ecosystem",
"points": 0,
"status": "excluded",
"details": [
{
"code": "not_reported_by_this_ecosystem",
"params": {}
}
],
"max_points": 20
}
]
}
],
"description": "Does the project have users, downloads, attention, and a welcoming setup for contributors?"
},
{
"key": "governance",
"band": "moderate",
"name": "Sustainability & Governance",
"value": 60,
"weight": 0.24,
"metrics": [
{
"key": "maintainer_resilience",
"band": "critical",
"name": "Maintainer resilience (bus factor)",
"note": null,
"notes": [],
"value": 10,
"inputs": {
"bus_factor": 1,
"contributors_sampled": 1,
"top_contributor_share": 1
},
"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 100% of commits",
"points": 0,
"status": "missed",
"details": [
{
"code": "top_contributor_share",
"params": {
"share": 100
}
}
],
"max_points": 22.5
},
{
"key": "contributor_breadth",
"name": "Contributor breadth",
"detail": "1 contributors",
"points": 1.4,
"status": "partial",
"details": [
{
"code": "contributors_sampled",
"params": {
"count": 1
}
}
],
"max_points": 13.5
},
{
"key": "openssf_scorecard_contributors",
"name": "OpenSSF Scorecard: Contributors",
"detail": "project has 0 contributing companies or organizations -- score normalized to 0",
"points": 0,
"status": "missed",
"details": [],
"max_points": 10
}
]
},
{
"key": "responsiveness",
"band": "excellent",
"name": "Issue & PR responsiveness",
"note": null,
"notes": [],
"value": 92,
"inputs": {
"merged_prs": 75,
"open_issues": 4,
"closed_issues": 35,
"issue_closed_ratio": 0.897,
"closed_unmerged_prs": 1
},
"components": [
{
"key": "issue_resolution",
"name": "Issue resolution",
"detail": "90% of issues closed",
"points": 41.9,
"status": "partial",
"details": [
{
"code": "issues_closed_share",
"params": {
"share": 90
}
}
],
"max_points": 46.75
},
{
"key": "pr_acceptance",
"name": "PR acceptance",
"detail": "75/76 decided PRs merged",
"points": 37.7,
"status": "partial",
"details": [
{
"code": "decided_prs_merged",
"params": {
"merged": 75,
"decided": 76
}
}
],
"max_points": 38.25
},
{
"key": "openssf_scorecard_code_review",
"name": "OpenSSF Scorecard: Code-Review",
"detail": "Found 4/5 approved changesets -- score normalized to 8",
"points": 12,
"status": "partial",
"details": [],
"max_points": 15
}
]
},
{
"key": "stewardship",
"band": "moderate",
"name": "Ownership & stewardship",
"note": null,
"notes": [],
"value": 56,
"inputs": {
"followers": 6,
"owner_type": "Organization",
"is_verified": null,
"owner_login": "encryption4all",
"public_repos": 16,
"account_age_days": 1992
},
"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": "6 followers of encryption4all",
"points": 6.1,
"status": "partial",
"details": [
{
"code": "owner_followers",
"params": {
"count": 6,
"login": "encryption4all"
}
}
],
"max_points": 25
},
{
"key": "track_record",
"name": "Track record",
"detail": "16 public repos, account ~5 yr old",
"points": 19.9,
"status": "partial",
"details": [
{
"code": "public_repos",
"params": {
"count": 16
}
},
{
"code": "account_age_years",
"params": {
"years": 5
}
}
],
"max_points": 25
}
]
},
{
"key": "package_maintenance",
"band": "excellent",
"name": "Package maintenance",
"note": null,
"notes": [],
"value": 100,
"inputs": {
"packages": [
"@e4a/pg-js"
],
"ecosystems": "npm",
"any_deprecated": false,
"min_days_since_publish": 4
},
"components": [
{
"key": "published_resolvable",
"name": "Published & resolvable",
"detail": "1 package(s) on npm",
"points": 25,
"status": "met",
"details": [
{
"code": "packages_published",
"params": {
"count": 1,
"ecosystems": "npm"
}
}
],
"max_points": 25
},
{
"key": "publish_recency",
"name": "Publish recency",
"detail": "latest publish 4 days ago",
"points": 35,
"status": "met",
"details": [
{
"code": "publish_recency",
"params": {
"days": 4
}
}
],
"max_points": 35
},
{
"key": "version_history",
"name": "Version history",
"detail": "51 published versions",
"points": 20,
"status": "met",
"details": [
{
"code": "published_versions",
"params": {
"count": 51
}
}
],
"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": "moderate",
"name": "Engineering Quality",
"value": 57,
"weight": 0.2,
"metrics": [
{
"key": "engineering_practices",
"band": "moderate",
"name": "Engineering practices",
"note": null,
"notes": [],
"value": 68,
"inputs": {
"has_ci": true,
"has_tests": true,
"has_editorconfig": false,
"has_linter_config": false,
"has_precommit_config": false
},
"components": [
{
"key": "ci_workflows",
"name": "CI workflows",
"detail": "3 workflow(s)",
"points": 24,
"status": "met",
"details": [
{
"code": "ci_workflows",
"params": {
"count": 3
}
}
],
"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": null,
"points": 0,
"status": "missed",
"details": [],
"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": 0,
"status": "missed",
"details": [],
"max_points": 6.4
},
{
"key": "openssf_scorecard_ci_tests",
"name": "OpenSSF Scorecard: CI-Tests",
"detail": "14 out of 14 merged PRs checked by a CI test -- score normalized to 10",
"points": 20,
"status": "met",
"details": [],
"max_points": 20
}
]
},
{
"key": "documentation",
"band": "at_risk",
"name": "Documentation",
"note": null,
"notes": [],
"value": 40,
"inputs": {
"topics": [],
"has_wiki": true,
"homepage": null,
"has_readme": true,
"has_docs_dir": false,
"has_description": false
},
"components": [
{
"key": "readme",
"name": "README",
"detail": null,
"points": 30,
"status": "met",
"details": [],
"max_points": 30
},
{
"key": "documentation_directory",
"name": "Documentation directory",
"detail": null,
"points": 0,
"status": "missed",
"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": 0,
"status": "missed",
"details": [],
"max_points": 10
},
{
"key": "topics",
"name": "Topics",
"detail": null,
"points": 0,
"status": "missed",
"details": [],
"max_points": 10
},
{
"key": "wiki",
"name": "Wiki",
"detail": null,
"points": 10,
"status": "met",
"details": [],
"max_points": 10
}
]
}
],
"description": "Are baseline engineering and documentation practices in place?"
},
{
"key": "security",
"band": "moderate",
"name": "Security",
"value": 53,
"weight": 0.16,
"metrics": [
{
"key": "security_posture",
"band": "moderate",
"name": "Security posture",
"note": "Excluded from scoring (no data or not applicable): Signed-Releases. Remaining weights renormalized.",
"notes": [
{
"code": "excluded_no_data",
"params": {
"components": [
"signed_releases"
]
}
},
{
"code": "weights_renormalized",
"params": {}
}
],
"value": 53,
"inputs": {
"source": "openssf_scorecard",
"checks_evaluated": 17,
"scorecard_version": "v5.5.0",
"checks_inconclusive": 1,
"scorecard_aggregate": 5.3
},
"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.8,
"status": "partial",
"details": [],
"max_points": 7.5
},
{
"key": "ci_tests",
"name": "CI-Tests",
"detail": "14 out of 14 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": "no effort to earn an OpenSSF best practices badge detected",
"points": 0,
"status": "missed",
"details": [],
"max_points": 2.5
},
{
"key": "code_review",
"name": "Code-Review",
"detail": "Found 4/5 approved changesets -- score normalized to 8",
"points": 6,
"status": "partial",
"details": [],
"max_points": 7.5
},
{
"key": "contributors",
"name": "Contributors",
"detail": "project has 0 contributing companies or organizations -- score normalized to 0",
"points": 0,
"status": "missed",
"details": [],
"max_points": 2.5
},
{
"key": "dangerous_workflow",
"name": "Dangerous-Workflow",
"detail": "no dangerous workflow patterns detected",
"points": 10,
"status": "met",
"details": [],
"max_points": 10
},
{
"key": "dependency_update_tool",
"name": "Dependency-Update-Tool",
"detail": "no update tool detected",
"points": 0,
"status": "missed",
"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 not detected",
"points": 0,
"status": "missed",
"details": [],
"max_points": 2.5
},
{
"key": "maintained",
"name": "Maintained",
"detail": "30 commit(s) and 0 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 detected",
"points": 5,
"status": "met",
"details": [],
"max_points": 5
},
{
"key": "pinned_dependencies",
"name": "Pinned-Dependencies",
"detail": "dependency not pinned by hash detected -- score normalized to 5",
"points": 2.5,
"status": "partial",
"details": [],
"max_points": 5
},
{
"key": "sast",
"name": "SAST",
"detail": "SAST tool is not run on all commits -- score normalized to 0",
"points": 0,
"status": "missed",
"details": [],
"max_points": 5
},
{
"key": "security_policy",
"name": "Security-Policy",
"detail": "security policy file not detected",
"points": 0,
"status": "missed",
"details": [],
"max_points": 5
},
{
"key": "signed_releases",
"name": "Signed-Releases",
"detail": "no releases found",
"points": 0,
"status": "excluded",
"details": [
{
"code": "no_data",
"params": {}
}
],
"max_points": 7.5
},
{
"key": "token_permissions",
"name": "Token-Permissions",
"detail": "detected GitHub workflow tokens with excessive permissions",
"points": 0,
"status": "missed",
"details": [],
"max_points": 7.5
},
{
"key": "vulnerabilities",
"name": "Vulnerabilities",
"detail": "1 existing vulnerabilities detected",
"points": 6.8,
"status": "partial",
"details": [],
"max_points": 7.5
}
]
},
{
"key": "high_risk_jurisdiction_exposure",
"band": "excellent",
"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"
],
"review_only_matches": 0,
"assessed_self_published_locations": 1
},
"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": 75,
"weight": 0,
"metrics": [
{
"key": "ai_agent_context",
"band": "excellent",
"name": "Agent context & guidance",
"note": null,
"notes": [],
"value": 85,
"inputs": {
"has_llms_txt": false,
"legible_history_share": 1,
"agent_instruction_files": [
"CLAUDE.md"
],
"agent_instruction_max_bytes": 10266
},
"components": [
{
"key": "agent_instructions",
"name": "Agent instructions",
"detail": "CLAUDE.md",
"points": 45,
"status": "met",
"details": [
{
"code": "file_list",
"params": {
"files": "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": "74 of 74 human commits state their intent (structured subject or explanatory body)",
"points": 40,
"status": "met",
"details": [
{
"code": "legible_history",
"params": {
"legible": 74,
"sampled": 74
}
}
],
"max_points": 40
}
]
},
{
"key": "ai_verify_loop",
"band": "moderate",
"name": "Verify loop (build / test / typecheck)",
"note": null,
"notes": [],
"value": 58,
"inputs": {
"has_nix": false,
"has_tests": true,
"lockfiles": [
"package-lock.json"
],
"has_dockerfile": false,
"typed_language": true,
"bootstrap_files": [],
"has_devcontainer": false,
"has_linter_config": false,
"typecheck_configs": [
"tsconfig.json"
],
"agent_commit_share": 0.13,
"toolchain_manifests": [],
"dependency_bot_commit_share": 0
},
"components": [
{
"key": "one_command_bootstrap",
"name": "One-command bootstrap",
"detail": null,
"points": 0,
"status": "missed",
"details": [],
"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": null,
"points": 0,
"status": "missed",
"details": [],
"max_points": 11
},
{
"key": "static_type_checking",
"name": "Static type checking",
"detail": "tsconfig.json",
"points": 11,
"status": "met",
"details": [
{
"code": "file_list",
"params": {
"files": "tsconfig.json"
}
}
],
"max_points": 11
},
{
"key": "reproducible_environment",
"name": "Reproducible environment",
"detail": "lockfile",
"points": 10,
"status": "met",
"details": [
{
"code": "file_list",
"params": {
"files": "lockfile"
}
}
],
"max_points": 10
},
{
"key": "demonstrated_agent_practice",
"name": "Demonstrated agent practice",
"detail": "13 of the last 100 commits agent-authored or agent-credited",
"points": 10,
"status": "met",
"details": [
{
"code": "agent_authored_commits",
"params": {
"count": 13,
"sampled": 100
}
}
],
"max_points": 10
},
{
"key": "automated_maintenance",
"name": "Automated maintenance",
"detail": "no automated dependency updates observed",
"points": 0,
"status": "missed",
"details": [
{
"code": "no_dependency_automation",
"params": {}
}
],
"max_points": 8
},
{
"key": "openssf_scorecard_pinned_dependencies",
"name": "OpenSSF Scorecard: Pinned-Dependencies",
"detail": "dependency not pinned by hash detected -- score normalized to 5",
"points": 5,
"status": "partial",
"details": [],
"max_points": 10
}
]
},
{
"key": "ai_code_legibility",
"band": "excellent",
"name": "Code legibility for models",
"note": null,
"notes": [],
"value": 100,
"inputs": {
"primary_language": "TypeScript",
"largest_source_bytes": 41642,
"source_files_sampled": 60,
"oversized_source_files": 0
},
"components": [
{
"key": "type_checkable_code",
"name": "Type-checkable code",
"detail": "TypeScript (statically typed)",
"points": 45,
"status": "met",
"details": [
{
"code": "statically_typed_language",
"params": {
"language": "TypeScript"
}
}
],
"max_points": 45
},
{
"key": "manageable_file_sizes",
"name": "Manageable file sizes",
"detail": "0/60 source files over 60KB",
"points": 55,
"status": "met",
"details": [
{
"code": "oversized_source_files",
"params": {
"kb": 60,
"sampled": 60,
"oversized": 0
}
}
],
"max_points": 55
}
]
}
],
"description": "How well is the repo equipped to be developed and maintained with AI coding agents? An independent, experimental badge — weight 0.0, so it is surfaced on its own and does not affect the overall health score."
}
],
"metrics_version": "1.13.0"
},
"warnings": [
"GitHub dependency-graph SBOM unavailable (404); the dependency graph may be disabled for this repository",
"deps.dev does not index npm:@e4a/pg-js@2.3.1; advisories assessed against the repository dependency graph instead"
],
"report_type": "repository",
"generated_at": "2026-07-22T02:15:30.722115Z",
"schema_version": "0.26.0",
"badge_url": "https://raw.githubusercontent.com/inspect-software/badges/main/v1/e/encryption4all/postguard-js.svg",
"full_name": "encryption4all/postguard-js",
"license_state": "absent",
"license_spdx": null
}