Skip to content

0.5.11 — An option a verb never reads is now refused, so a misspelled password on key export can no longer leave a private key unprotected. - #175

Merged
dotCooCoo merged 43 commits into
mainfrom
release/v0.5.11
Aug 19, 2026
Merged

Conversation

@dotCooCoo

@dotCooCoo dotCooCoo commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Summary

Eleven verbs accepted any options object and read only the keys they knew, so a typo, or an option borrowed from a sibling verb, did nothing and reported nothing. pki.key (encrypt, decrypt, export, import, generate, publicFromPrivate), pki.path.validate, pki.path.build, pki.lint.certificate and pki.ocsp (buildRequest, sign, verify) now refuse a key they do not read.

The one that mattered: pki.key.export serializes and has never encrypted, so export(privateKey, { password }) wrote the private key in the clear from a call site that read as though it were protected. It now refuses and names pki.key.encrypt(key, password), whose result is what to export.

Where two verbs spell one idea differently the refusal says which is which — path.validate takes trustAnchor and path.build takes trustAnchors; key.encrypt chooses iterations while key.decrypt caps maxIterations; the OCSP nonce means something different in each of buildRequest, sign and verify.

Three defects underneath the first one

Each was found by review after the fix above, and each was a way past it.

  1. The check ran after the default. opts = opts || {} preceded the shape check, and || treats false, 0, "" and NaN as absent, so those four arrived as {} and were accepted. pki.cms.sign, pki.cms.countersign and pki.cms.verify had already shipped that way. Normalization is now one primitive that returns the value, so the safe order is the only order available.

  2. The check could not see half the property surface. It walked Object.keys — own enumerable names — while opts.foo resolves through prototypes, non-enumerable properties and accessors. A prototype value, a non-enumerable own value, and a class getter each answered opts.password while showing the check nothing, and each returned an unprotected key. Enumerability is now out of the test entirely: it recorded how a member was written (class syntax defines methods non-enumerable, Proto.m = fn defines them enumerable), not what it means.

  3. Accepting a wider surface is half a change. path.build recognized an inherited requireRevocation: true and then built its forwarding set with Object.keys, so it never reached a single internal validate — a caller who demanded a determined revocation result could have got a valid path without one. Both copies now read one exported definition.

Deliberately allowed

An instance of a caller's own class stays a usable options bag; this toolkit had already settled that (guard.bytes.snapshotDeep copies one and keeps its prototype so its methods resolve). So an inherited data property holding a function is skipped as behaviour. The residual is stated in the code: an unknown option supplied as an inherited method is not reported — its value would be a function, and the function-valued options here are passed as own properties.

Test plan

  • eslint --max-warnings 0 . — clean
  • npm run gates — codebase-patterns, comment blocks, api snapshot, spelling
  • SMOKE_PARALLEL=64 node test/smoke.js — 12644 checks across 118 files
  • node scripts/test-integration.js — interop green (run by release.js push)
  • gitleaks — no leaks
  • Every guard RED-proven: neutered, watched its named vector fail, restored
  • CI green

No wire format, encoder or decoder changed. The API surface snapshot is unchanged; the behavior change is that a call passing an option that previously did nothing now fails, naming the key.

… `password` on key export can no longer leave a private key unprotected.

Eleven verbs accepted any option object and read only the keys they knew, so a typo, or an option borrowed from a sibling verb, did nothing and said nothing. The worst of those was `pki.key.export`, which does not encrypt: passing it a `password` wrote the private key in the clear from a call site that read as though the key were protected. Each verb now refuses a key it does not read, and the message names the option or the verb the caller most likely wanted.

Fixed:
  - `pki.key.export(key, { password })` no longer writes an unprotected private key. Export serializes; it has never encrypted. The option was ignored, so the file on disk was a plaintext PKCS#8 while the call site named a password. It now throws `key/bad-input` naming `pki.key.encrypt(key, password)`, whose result is what to export.
  - An options argument of `false`, `0`, `""` or `NaN` is refused rather than read as no options at all. The check that an options argument is an object ran after the argument had already been replaced by an empty object, and that replacement treated any falsy value as absent, so those four reached the body as `{}`. `pki.cms.sign`, `pki.cms.countersign` and `pki.cms.verify` carried this before this release; the verbs gaining option checks here would have inherited it. `null` and `undefined` still mean no options, and a Buffer in the options position is now named as the argument-order mistake it is.

Changed:
  - `pki.key` (encrypt, decrypt, export, import, generate, publicFromPrivate), `pki.path.validate`, `pki.path.build`, `pki.lint.certificate` and `pki.ocsp` (buildRequest, sign, verify) throw `<domain>/bad-input` on an option they do not read, instead of ignoring it. A call passing an option that did nothing before will now fail; the message names the unknown key, so the fix is to correct or drop it.
  - Where two verbs spell the same idea differently, the refusal says so. `pki.path.validate` takes `trustAnchor` and `pki.path.build` takes `trustAnchors`, and each names the other, because carrying the wrong spelling between them previously bought no anchoring and no error. `pki.key.encrypt` chooses `iterations` while `pki.key.decrypt` caps `maxIterations`; `pki.ocsp` spells the nonce three ways across buildRequest, sign and verify because it means three different things.
  - `pki.path.build` accepts every `pki.path.validate` option, since it forwards them to each internal validation. That union is derived from validate's own list rather than restated, so the two cannot drift apart.
Signed-off-by: dotCooCoo <RobertLeeLW@gmail.com>
@dotCooCoo

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 01cc105e68

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread lib/key.js
The refusal of unknown options walked own enumerable names, which is the subset
`Object.keys` reports and not the set a later `opts.foo` can resolve. Two
ordinary JavaScript objects answered the lookup while showing the check nothing:
`Object.create({ password: "pw" })` puts the value on the prototype, and
`Object.defineProperty(o, "password", { enumerable: false })` puts it where the
enumeration cannot see it. Either one meant `pki.key.export` accepted the bag,
ignored the password it could read, and returned the private key in the clear --
the case the refusal exists to prevent, reached by a different object shape.
Both were reproduced against the shipped verb before the fix and after it.

The check now reads every own name, enumerable or not, plus the inherited
enumerable ones. Inherited NON-enumerable members are deliberately left alone.
`constructor`, and the methods class syntax defines, are machinery no caller
means as an option, and this repository has already settled that a class
instance is a legitimate options bag -- guard.bytes.snapshotDeep copies one and
keeps its prototype so its methods still resolve. A rule that refused non-plain
objects, or that flagged everything up the chain, would have contradicted that
and rejected calls that work today. The first attempt did exactly that and the
suite caught it on `constructor`.

The vectors cover both directions: the two hidden spellings are refused, and a
null-prototype bag and an inherited KNOWN option are still accepted, so the
widened check cannot quietly start rejecting valid callers.

Signed-off-by: dotCooCoo <RobertLeeLW@gmail.com>
@dotCooCoo

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bb4ed35ac8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread lib/guard-identifier.js Outdated
Comment thread lib/path-validate.js Outdated
Two more shapes reached `opts.password` while the check saw nothing, and both
meant `pki.key.export` returned an unprotected private key. A class getter is
non-enumerable, so a rule keyed on enumerability missed it. A value that is both
inherited AND non-enumerable was missed by every earlier reading.

Enumerability is gone from the test, because it recorded how a member was
WRITTEN rather than what it means: class syntax defines methods non-enumerable,
`Proto.m = fn` defines them enumerable, and the previous rule accepted one style
and rejected the other. What is skipped now is a name inherited as a data
property holding a function -- a method, either way it was written, including
`constructor`. An accessor is never skipped: a getter exists to answer with a
value, which is what an option is. An instance of a caller's own class remains a
usable options bag, which this toolkit had already settled elsewhere.

One case is traded away and is written down rather than left implicit: an
unknown option supplied as an inherited method is not reported. Its value would
be a function, and the function-valued options here are passed as own
properties, so reaching it needs a caller to both misspell one and put it on a
prototype.

The second half is that accepting a wider surface is only half a change.
`pki.path.build` recognized an inherited option and then built its forwarding
set with `Object.keys`, so `Object.create({ requireRevocation: true })` passed
the check and never reached a single internal validate -- a caller who demanded
a determined revocation result could have got a valid path without one. Both
copies now read the same surface, from one exported definition, so the set that
is accepted and the set that is forwarded cannot differ. Fixing only the second
copy left the first one dropping it, which is how the first attempt at this went.

Signed-off-by: dotCooCoo <RobertLeeLW@gmail.com>
@dotCooCoo

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2604012b57

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread lib/guard-identifier.js Outdated
Comment thread lib/guard-identifier.js Outdated
…c chain

A name is skipped as a method when it is a data property holding a function
that the prototype chain above it also supplies under that name. Ownness no
longer decides it. Ownness records whether the object has been through
guard.bytes.snapshotDeep, which copies every readable name onto the copy and
keeps the prototype, so the same method is inherited before the snapshot and
own after it. A rule keyed on ownness called that method behavior at one check
site and an unknown option at the next, and every check running after a
snapshot refused an options bag that is an instance of the caller's own class.
Value identity against the chain above answers the same on both sides, so one
rule now covers every call site rather than the ones that happen to run first.

Object.prototype is compared against a snapshot of its own names taken while
guard-identifier loads. Stopping the walk at Object.prototype excluded the
whole object, so a runtime carrying Object.prototype.password answered
opts.password on a plain {} and pki.key.export returned an unprotected key for
a bag holding nothing. The built-ins are skipped by identity and any name added
afterwards is reported. A name planted before the module loads is baked into
the snapshot; require order is the limit of what this can see.

A prototype chain is finite only while it is acyclic, and a Proxy whose
getPrototypeOf trap returns the proxy makes one while its target stays
extensible. Reading an object's option names, copying it, and every instanceof
inside the copy walk that chain, and none of them terminated: the first two
looped and the third exhausted the stack, so a raw RangeError escaped from a
guard whose contract is a typed error. Reading the names stops at an object
already visited, which loses nothing because a second lap yields no new name,
and copying refuses the shape with the caller's own code.

Each half is proven by neutering it and watching a named vector fail: the
promoted-method reading against cms.sign and ocsp.buildRequest, the inherited
reading against key.export, and each cycle test against its own guard suite.

The refusal messages and the comments added by this branch drop the
mid-sentence aside and the capitalized emphasis.

Signed-off-by: dotCooCoo <RobertLeeLW@gmail.com>
@dotCooCoo

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ac23119df0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread lib/guard-identifier.js
…wers

A Proxy resolves ownKeys and get from two independent traps, so the two need
not agree. One reporting no keys while its get returns "pw" for password
presents an object every enumeration calls empty and every read calls
populated: pki.key.export accepted such a bag and returned 48 bytes of
plaintext PKCS#8, which is the case the unknown-option refusal exists to
prevent, and guard.bytes.snapshotDeep copied it to an object holding nothing
while the original still answered, losing a supplied field with no fault
raised.

No walk closes that. The readable names are whatever a trap decides at the
moment it is asked, and asking again may differ, so the enumeration is not made
more thorough and the object is refused. That is the rule guard-bytes already
states for a value it cannot copy: an argument is copied or refused, never
half-taken.

The test is util.types.isProxy, by identity. Probing traps for a contradiction
is a race the liar wins, since it can answer consistently for as long as the
check looks and differ afterwards. A Proxy with no traps of its own is refused
on the same rule rather than admitted for behaving today.

Both refusals are proven by neutering each and watching its named vector fail:
key.export and assertKnownKeys for the options door, snapshotDeep for the copy.

Signed-off-by: dotCooCoo <RobertLeeLW@gmail.com>
@dotCooCoo

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 836ff9adee

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread lib/guard-identifier.js Outdated
Comment thread lib/guard-bytes.js Outdated
A Symbol-named option is reported. Both walks enumerated with
Object.getOwnPropertyNames, which never returns one, so a Symbol key was
accepted in silence and the copy dropped it before the check read the copy.
Both now use Reflect.ownKeys, and the refused name reaches the message as its
description, before either the default wording or a caller's own builder sees
it, since JSON.stringify returns undefined for a Symbol.

A name planted on Object.prototype before this package loads is reported,
whether it holds a value or a function. The built-ins were recognized by
reading Object.prototype at load, which is the polluted runtime answering the
question about itself, so a name already there was taken for one and key.export
returned a plaintext private key. They are now the twelve members the language
specifies, each still required to have the shape a real one has: a data
property holding a function, or the __proto__ accessor. Object.prototype is
decided there and the method rule does not run on it, since that rule exempts
an inherited function and a planted one is both. A guard-identifier test
compares the list against the live object, so an engine that adds a member
fails there rather than reporting it to every caller as an unknown option.

A verb called with no options is handed a bag with no prototype, so it inherits
nothing and its caller is never refused for a name they did not write.

A Proxy is refused anywhere on the chain rather than only as the object itself.
An ordinary object inheriting from one resolves the read through it while not
being one, which left a line of indirection open in both the check and the copy.

Copying no longer drops a name the caller added. A Date came back as its
instant alone, and an own `constructor` was skipped along with the inherited
one a class defines; in both cases the copy held nothing while the original
still answered.

Each is proven by neutering it and watching its named vector fail. The
pre-load plants run in a child process, since the plant has to precede the
require that takes the snapshot.

Signed-off-by: dotCooCoo <RobertLeeLW@gmail.com>
@dotCooCoo

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6fbc720bd5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread lib/guard-bytes.js Outdated
Reading a prototype chain runs a getPrototypeOf trap, so every walk of one runs
the caller's code. The copy walked the chain looking for a cycle before it had
decided the value was a Proxy at all, and a trap that throws then left as its
own raw Error from a boundary whose whole contract is a typed one. The Proxy
test now comes first, ahead of the cycle walk and of every instanceof below it,
and it reaches getPrototypeOf only on links it has already cleared.

The cycle test stays underneath as a second line and is unreachable while the
first one holds: setPrototypeOf refuses to build a cycle out of ordinary
objects, so a Proxy is the only way to have one.

readableNames takes the caller's error factory, like every other function in
the guard family. It walks the chain, and it was the one door that could not
refuse a Proxy before doing so, which left a hostile trap escaping untyped from
a function this module exports for exactly the caller who has not checked yet.
Both call sites in path-validate pass their own class and code.

A primitive in the options position is refused by the verb that checks types
rather than raising a TypeError about Reflect.ownKeys, which the caller never
called. The walk starts above the value instead of on it.

The error factory is checked at entry rather than at the moment it is needed,
which is the one moment something has already gone wrong. What that catches is
a missing or non-callable factory; a class cannot be told from a factory by
typeof, so calling E without new stays a convention its own vector pins.

Each is proven by neutering it and watching its named vector fail.

Signed-off-by: dotCooCoo <RobertLeeLW@gmail.com>
@dotCooCoo

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

throw _raise(ErrorClass, code, label + ": a " + (v.constructor && v.constructor.name || "value") +

P2 Badge Avoid reading constructor while rejecting opaque values

When an unsafe opaque value has a hostile constructor accessor, this diagnostic reads it after deciding to reject the value, allowing the accessor's raw exception to escape instead of the caller's typed domain error. For example, a RegExp with an enumerable foo property and an own get constructor() { throw new Error("boom"); } makes snapshotDeep throw that untyped Error; through any fixedCall producer such as pki.cms.sign, this bypasses the promised <domain>/bad-input boundary. Format the kind without consulting another caller-controlled property, or wrap this read through _raise.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@dotCooCoo

Copy link
Copy Markdown
Contributor Author

The mechanism does not reproduce on either runtime, and the suite claim does not hold, but the fragility behind it is real and is now pinned by vectors.

Measured, on a freshly generated key and on an imported one, for Ed25519, ECDSA P-256 and RSASSA-PKCS1-v1_5, before and after reading type / extractable / algorithm / usages (where a lazy cache would land):

node v24.19.0  own symbols = 0, enumerable = 0   (every case)
node v26.6.0   own symbols = 0, enumerable = 0   (every case)

No Symbol(kCachedAlgorithm) or Symbol(kCachedKeyUsages) appears on a node:crypto CryptoKey in either version.

On the suite claim: npm test exits 0 with 12706 checks, and guard-bytes.test.js (174) and x509-sign.test.js (197) both pass. The full smoke also passes inside node:24-alpine, which is the CI runtime, with 12677 checks including both of those files.

What the report gets right is that enumerability of an implementation's own slots is an assumption about a runtime this library does not control, and widening the walk to Reflect.ownKeys made that assumption load-bearing for symbols where before it applied only to names.

Excluding symbols from the opaque pass-through test is not the remedy: an opaque handle is passed through by reference, so a field a caller hangs on one stays theirs to change after every check has read it, and a symbol key would then be the way around the refusal that exists for exactly that. There is also no way to tell an implementation's symbol from a caller's.

So enumerability stays the line, the same line already used for names, and both directions are now vectors on a real CryptoKey: one asserts a platform key carries no enumerable own symbol after its surface has been read and still passes through by reference, the other asserts a key carrying a caller-added symbol field is refused. If a future runtime ever does cache under an enumerable symbol, the first fails at that upgrade instead of an operator finding their keys refused in production.

…al key

The property walks report symbol keys, so a caller cannot hang a field on an
argument under one and have every check pass over it. That widening reaches the
opaque pass-through test, which decides whether a handle carries caller-added
fields, and enumerability is the line there for a symbol exactly as it is for a
name: an implementation's own slots are non-enumerable, and what a caller adds
they add by assignment.

That line is an assumption about a runtime this library does not control, so it
is now measured rather than assumed. A node:crypto CryptoKey carries no
enumerable own symbol on Node 24 or 26, generated or imported, for Ed25519,
ECDSA and RSA, before or after the surface reads where a lazy cache would land.
Two vectors hold both ends: a platform key still passes through by reference
after its surface has been read, and a key carrying a caller-added symbol field
is refused. A runtime that ever did cache under an enumerable symbol would fail
the first at the upgrade rather than refusing an operator's key in production.

Excluding symbols there is not available: a handle is passed through by
reference, so a field hung on one stays the caller's to change after every check
has read it, and no test tells an implementation's symbol from a caller's.

A symbol behaves as a name in the rest of the copy too, which is the point of
the widening rather than a side effect: a class method declared under a symbol
survives and still runs, and an accessor that throws is the caller's typed error
under either kind of key.

Signed-off-by: dotCooCoo <RobertLeeLW@gmail.com>
@dotCooCoo

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e7401eaf71

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread lib/path-validate.js Outdated
…prototype

pki.path.build forwards each validate option to every internal validate call,
and chose what to forward by indexing a build-only table with the option name.
A table read that way answers for any name Object.prototype carries, so on a
runtime carrying softFail the caller's own value was taken for a build-only
option and left out of the forwarded set. That set was a plain object, so the
pass that reads it back found the inherited value instead. A caller who wrote
softFail: false could receive a valid path built on an undetermined revocation
result they had refused to waive.

Both halves are fixed and each is proven by its own vector. Own membership
decides whether a name is build-only, which is what carries the caller's stated
value through in either direction: a stated false must not be dropped, and a
stated true must not be either. The forwarded set and the per-anchor options
built from it have no prototype, so a build-only name excluded from them stays
excluded rather than being re-acquired and handed to validate, which does not
accept it and would refuse the whole call.

The vectors run in a child process, since the plant has to precede the require
that reads the prototype. test/helpers/polluted-prototype-preload.js is that
plant, and it names options the verb recognizes on purpose: an unrecognized one
is already refused, and a name the caller never states is genuinely what their
bag answers, so neither settles anything. The case in between is the one that
needed pinning.

The wider shape, a registry indexed by a caller-supplied name, is a separate
sweep: constructor and toString are truthy in any such table with no pollution
at all.

Signed-off-by: dotCooCoo <RobertLeeLW@gmail.com>
@dotCooCoo

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Breezy!

Reviewed commit: 46903a4f37

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

An array, a byte view, a Map, a Set or an ArrayBuffer used as an options bag
reports only what the caller added to it. A verb reads an option by name
whatever the argument's type, so these shapes are supported, but their own
surface is structural: length is intrinsic on an empty array and the prototypes
carry buffer, byteLength, size and BYTES_PER_ELEMENT. Reading the whole chain
reported all of it and the verb then refused a shape it supports. length and
the indices are skipped for an array and a byte view only, since the other
kinds have no own length and skipping the name there would discard one the
caller had put on themselves. guard-bytes states the same rule for which names
it copies, and the two are one rule seen from each side.

pki.path.build builds the set it forwards from the validate-option contract
rather than by enumerating the bag. Enumerating reads whatever the property
walk reports, and that walk skips a method so a caller's class keeps its own; a
known option whose value were a bare function would be indistinguishable from
one and would be dropped. No option takes a bare function today, so this is a
hardening rather than a live defect and no vector fails without it. It is here
because asking the contract cannot drop a known option whatever its value.

The spelling gate reads the files git does not track yet, alongside the ones it
does. Built on `git ls-files` alone it answered for the committed tree, so a
file written in the same session was invisible locally and read for the first
time by CI, which is how a British spelling in a new test helper cost a full
run on a tree whose gates were green. Its canary now asserts the file set
itself, and clears its probe before writing as well as after, so a run that
dies partway cannot leave a planted misspelling in the repository root for the
next run and the linter to trip over.

Signed-off-by: dotCooCoo <RobertLeeLW@gmail.com>
@dotCooCoo

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 46e15c51ca

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread lib/guard-bytes.js Outdated
Comment thread lib/guard-bytes.js Outdated
…he property can still move, so a key handle reaches the verb it was passed to and a field the caller can still change is refused; and a Date copy answers a Date method with the language's rather than a method written onto the value

Signed-off-by: dotCooCoo <RobertLeeLW@gmail.com>
@dotCooCoo

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5ad8aa923c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread lib/guard-bytes.js Outdated
…an HMAC key whose handle holds raw bytes reaches the verb it was passed to alongside the signing keys

Signed-off-by: dotCooCoo <RobertLeeLW@gmail.com>
@dotCooCoo

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a28a02b984

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread lib/guard-identifier.js Outdated
… field inherited from an object a caller built over the shared typed-array prototype is reported as the unknown option it is

Signed-off-by: dotCooCoo <RobertLeeLW@gmail.com>
@dotCooCoo

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Nice work!

Reviewed commit: 11bf5203f6

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

…efusal

Signed-off-by: dotCooCoo <RobertLeeLW@gmail.com>
@dotCooCoo

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 59faa057e3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread lib/guard-bytes.js
…uestion, so a throwing trap cannot leave the copy carrying the caller's own error

Signed-off-by: dotCooCoo <RobertLeeLW@gmail.com>
@dotCooCoo

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: dc45bd1a26

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread lib/guard-bytes.js
Comment thread lib/inspect.js Outdated
…olds, so a Date carrying its own calendar methods cannot change what is printed beside a certificate

Signed-off-by: dotCooCoo <RobertLeeLW@gmail.com>
@dotCooCoo

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 759a70c6d3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread lib/guard-bytes.js Outdated
…where nothing can be put in front of it, since an extensible value takes an own property of the same name at any time

Signed-off-by: dotCooCoo <RobertLeeLW@gmail.com>
@dotCooCoo

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Already looking forward to the next diff.

Reviewed commit: 5681e3cb4e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

…re that implementation keeps its internals under a symbol, since no verb reads an option under one; a name a verb could read is still refused on the same handle

Signed-off-by: dotCooCoo <RobertLeeLW@gmail.com>
@dotCooCoo

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 34386bb84e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread lib/guard-bytes.js Outdated
…n the name, so a plain field named after a Date method reaches the unknown-option check instead of vanishing

Signed-off-by: dotCooCoo <RobertLeeLW@gmail.com>
@dotCooCoo

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Chef's kiss.

Reviewed commit: 19ddd0c488

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@dotCooCoo
dotCooCoo merged commit 5a77a64 into main Aug 19, 2026
68 checks passed
@dotCooCoo
dotCooCoo deleted the release/v0.5.11 branch August 19, 2026 06:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant