@cosyte/mllp 0.0.8 → 0.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -9,8 +9,12 @@ this file is maintained by hand (Changesets handles the version bump and publish
9
9
 
10
10
  ## [Unreleased]
11
11
 
12
- The first pre-alpha release (`0.0.1`) will ship the v1 MLLP transport surface below. The package
13
- begins its public history at `0.0.x`, per the cosyte version ladder (`0.0.x` until first alpha).
12
+ `@cosyte/mllp` is published on npm and stays on the `0.0.x`-until-first-alpha ladder, so the API can
13
+ change with no deprecation cycle. The sentence this replaces said the first pre-alpha release "will
14
+ ship" the surface below, which stopped being true the day the package first published and had been
15
+ false ever since. **No version literal is written here, on purpose**: what is published is a fact
16
+ about the registry (`npm view @cosyte/mllp version`) and about `package.json`, and a number copied
17
+ into prose here is a claim that goes stale on its own.
14
18
 
15
19
  ### Added
16
20
 
@@ -76,6 +80,76 @@ begins its public history at `0.0.x`, per the cosyte version ladder (`0.0.x` unt
76
80
 
77
81
  ### Security
78
82
 
83
+ - **The PHI scanner enumerates a staged rename, reads a blob that replaced a walk root, and
84
+ refuses a root that is not a directory (`PHI-SCAN-RENAME-BLIND-AT-PRECOMMIT`).** Three holes, two
85
+ of them on `--staged`, which is this repo's **pre-commit** gate. All three were reproduced on
86
+ `2252d33` before anything was touched, with a synthetic name-bearing payload kept outside both
87
+ walk roots.
88
+
89
+ **The rename hole.** `R` (rename) and `C` (copy) are the only `--raw` statuses carrying a second
90
+ path, and `--diff-filter=AMT` deletes such a record outright, so the route never saw one.
91
+ Measured: `git mv <link> test/<name>` staged as `:120000 120000 <sha> <sha> R100` and `--staged`
92
+ printed `OK, no hits` with a mode-`120000` entry sitting under a scan root; a rename that also
93
+ substituted a real name staged as a scored rename and passed the same way, over live `PID-5` / `PID-7` /
94
+ `PID-3` values in the destination blob. The remedy is **`--no-renames`**, and it needs no work on
95
+ the record shape at all: the destination arrives as an ordinary single-path `A` and the source as a
96
+ `D` the filter already drops. The enumeration is a strict **superset** of the previous one, and
97
+ that is pinned on the records rather than on an exit code: a stage with no rename in it hands the
98
+ scanner byte-identical raw output either way. The two-field
99
+ stride becomes **structural** rather than conditional, because with detection off git cannot emit
100
+ an `R` or a `C` at all. Verified under `diff.renames` set to `true`, `copies`, `false` and `1`,
101
+ and under `diff.renameLimit=1`, so the caller's own configuration cannot reopen it. **The earlier
102
+ disclosure that admitting these "needs the two-path record shape handled, which is a scope
103
+ decision" was wrong**, and it was wrong because it was carried in from a sibling rather than
104
+ measured here.
105
+
106
+ **The root-replacing blob.** The refusal path already matched a scan root's own name, because an
107
+ entry at exactly `test` REPLACES the root instead of sitting in it. The read filter did not: it
108
+ wanted the `test/` prefix. So a mode-`100644` blob staged at exactly `test` was in scope for one
109
+ and out of scope for the other, and `--staged` exited 0 over the same three fields. Both read
110
+ predicates now admit the root's own path. **Admitting it to the read set was only half of it**:
111
+ `looksLikeHl7` decides what scan a target earns and a path named `test` matches none of the
112
+ fixture-like extensions, so a draft read the blob and still reported clean over a `PID`. An entry
113
+ that replaces a root is judged with **that root's own limits**, so `test` earns the structured
114
+ HL7 scan while `src` keeps the conservative dashed-SSN + email pass, exactly as a file inside
115
+ either root would. Both directions are pinned.
116
+
117
+ **The non-directory root.** A walk root that resolves to a **file** (a regular file at `test`, or
118
+ a link to one) threw `ENOTDIR` straight out of `readdirSync`, uncaught. Node exits **1** on an
119
+ uncaught throw, which is the code this contract reserves for "hits found", so the gate published
120
+ a finding it had never made: a false positive that reads as actionable, which is a different and
121
+ worse failure than a crash. A **dangling** link at a root was the silent half of the same shape,
122
+ because `existsSync` follows: the walk returned and the sweep reported `OK` over the entire
123
+ corpus that root stands for, with the `observed === 0` backstop unable to fire while the other
124
+ root still had files. Both refuse now (exit 2), naming the root and its kind and **never the link
125
+ target**. Reading whatever sits there instead was refused as the remedy: what is missing in the
126
+ working tree is a **tree**, and one file read in its place is evidence about that file rather
127
+ than about the corpus it replaced. `staged` mode does read such a blob, because the index holds
128
+ no directories to lose. `walk()` also no longer lets any `readdir` failure leave the process:
129
+ everything but `ENOENT` is now a refusal that names the directory.
130
+
131
+ **Two more routes into the same false finding, both `PRE-EXISTING`, both closed here.** A
132
+ **missing** allow-list threw its refusal past every `catch` in `main`, and an **unreadable**
133
+ allow-list or override log threw a raw `EACCES`: both exited 1. Nothing ever passed the gate that
134
+ way, because non-zero still blocks the commit, but the code said the wrong thing. All three exit
135
+ 2 now: the missing-allow-list route is answered where it arises, and the two unreadable ones reach
136
+ a process-level guard that turns anything still unaccounted for into exit 2. A bad root and an
137
+ unscannable entry under the other root also come out in one refusal now, rather than one per
138
+ run.
139
+
140
+ **What did not change, deliberately, and is pinned so it stays a decision.** A root that is a
141
+ link to a **directory** is still followed and is still link-neutral (the tree beyond it is
142
+ scanned as the root it replaced would have been, with that root's own limits). An **absent** root
143
+ is still legitimate and still exits 0, which is the control that separates it from a dangling
144
+ one. Explicit-path mode still reads through a link. And the `test/` walk still excludes `.ts`
145
+ sources, which is a separate open question about walk-root scope and is not touched here.
146
+
147
+ 16 new cases; **12 of them run red against the base scanner** and the other four are deliberate
148
+ controls (an ordinary stage, an absent root, a linked-directory root, and the record-identity
149
+ comparison, which is a statement about git and holds on both trees). Negative-controlled across packages too: the identical stage reports
150
+ four HL7 field hits through this scanner and nothing through a sibling's, so the evidence is
151
+ about this package rather than about the harness.
152
+
79
153
  - **The PHI scanner refuses a non-regular entry under a scan root, on both routes
80
154
  (`PHI-SCAN-SYMLINK-BLIND-ON-BOTH-ROUTES`).** A symbolic link under `test/` or `src/` read CLEAN on
81
155
  **both** enumerating routes, so a link pointing at a PHI-bearing file passed the gate twice over.
package/dist/index.cjs CHANGED
@@ -3438,7 +3438,7 @@ async function createStarterClient(opts) {
3438
3438
  }
3439
3439
 
3440
3440
  // src/index.ts
3441
- var VERSION = "0.0.8";
3441
+ var VERSION = "0.0.9";
3442
3442
 
3443
3443
  exports.Connection = Connection;
3444
3444
  exports.FrameReader = FrameReader;