{"_id":"@bonniernews/stayput","_rev":"4-658e3814bb3aa56cfd7eedbfdefab91b","name":"@bonniernews/stayput","dist-tags":{"latest":"0.1.4"},"versions":{"0.1.0":{"name":"@bonniernews/stayput","version":"0.1.0","license":"MIT","_id":"@bonniernews/stayput@0.1.0","maintainers":[{"name":"markusn","email":"markus@botten.org"},{"name":"mxtr","email":"max.olofsson@bonniernews.se"},{"name":"matsrorbecker","email":"mats@rorbecker.com"},{"name":"dan.karlsson","email":"dan.arola@bonniernews.se"},{"name":"adil.aboulkacim","email":"adil.aboulkacim@bonniernews.se"},{"name":"marie-winther","email":"marie.winther@bonniernews.se"},{"name":"marcusgronblad","email":"marcus.gronblad@bonniernews.se"},{"name":"axeljohanssonbonniernews","email":"axel.johansson@bonniernews.se"},{"name":"karlbergc","email":"christoffer.karlberg@bonniernews.se"},{"name":"chavah","email":"chavah.forler@bonniernews.se"},{"name":"joelekman","email":"joel.ekman@bonniernews.se"},{"name":"adam.hakansson","email":"adam.hakansson@bonniernews.se"},{"name":"hilleso","email":"marcus.hilleso@expressen.se"},{"name":"gusliden","email":"gusliden@gmail.com"},{"name":"andreas.samuelsson","email":"andreas.samuelsson@bonniernews.se"},{"name":"jzachrisson","email":"jesper.zachrisson@bonniernews.se"},{"name":"norla","email":"mattias.norlander@gmail.com"},{"name":"varneynz","email":"simon.varney@bonniernews.se"},{"name":"aliceboberg","email":"alice.boberg@bonniernews.se"},{"name":"johark","email":"johan.arkad@bonniernews.se"},{"name":"nifo","email":"niklas.forsstrom@bonniernews.se"},{"name":"herbola","email":"herman.jansson@bonniernews.se"},{"name":"vitryssen","email":"andre.nordlund@bonniernews.se"},{"name":"takolander","email":"william.takolander@bonniernews.se"},{"name":"morre","email":"marten.persson@hdsydsvenskan.se"},{"name":"mikael.mattsson","email":"mikael.mattsson@bonniernews.se"},{"name":"schristianssonbn","email":"seb.christiansson@bonniernews.se"},{"name":"mattiasobn","email":"mattias.olla@bonniernews.se"},{"name":"daghall","email":"markus@daghall.se"},{"name":"erandersson","email":"ericandersson1@gmail.com"},{"name":"jonaek","email":"jonathan.ek@bonniernews.se"},{"name":"amundsentb","email":"amundsentb@gmail.com"},{"name":"drgeobn","email":"daniel.rasmussen@bonniernews.se"},{"name":"oscartholander","email":"oscar@tholander.nu"},{"name":"peterpettersson","email":"peter.pettersson@bonniernews.se"},{"name":"liroliro","email":"basistoscar@gmail.com"},{"name":"torkelberget","email":"andreas.egneblad@bonniernews.se"},{"name":"johankasperi","email":"johan@kasperi.se"},{"name":"jon.eldeklint","email":"jon.eldeklint@bonniernews.se"},{"name":"nblom","email":"niklas.blom@bonniernews.se"},{"name":"jackesa","email":"jakob.pedersen@bonniernews.se"},{"name":"ollenolle","email":"olle.nordin@bonniernews.se"},{"name":"emilbjorklund","email":"emil@thatemil.com"},{"name":"lilianj","email":"japlilian@gmail.com"},{"name":"kristofferjansson","email":"kristoffer.jansson@bonniernews.se"},{"name":"kitgus","email":"kit.gustavsson@bonniernews.se"},{"name":"odynvolk","email":"alexi.rahman@r76.se"}],"homepage":"https://github.com/BonnierNews/stayput#readme","bugs":{"url":"https://github.com/BonnierNews/stayput/issues"},"dist":{"shasum":"a00bd4ac081f2d0b4a47aeaae44badc9eb34a271","tarball":"https://registry.npmjs.org/@bonniernews/stayput/-/stayput-0.1.0.tgz","fileCount":6,"integrity":"sha512-WH1L9mM00z3WcFaZSOZKenlcfMag6EeCqk42BNLt1juUPS8T49M69F3BZmiykKhcdsBr64HzigGJBUIYL+b4fQ==","signatures":[{"sig":"MEUCIEVIHk0qH5mDcGW+VWzR8gLgfXcNLOnyDe80JBANfJjaAiEA0lDFd9xtHbombf2qkgd6G2ki35CqZIxIk8zj5QQHxlU=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":17270},"type":"module","engines":{"node":">=20.6.0"},"exports":{".":"./index.js","./mocha":"./mocha.js","./register":"./register.js","./package.json":"./package.json"},"gitHead":"f84d84937b345cb1961aafed9234c4ff0ebf220c","scripts":{"test":"node test.js","prepublishOnly":"npm test","test:integration":"node test-integration.js"},"_npmUser":{"name":"jon.eldeklint","email":"jon.eldeklint@bonniernews.se"},"repository":{"url":"git+https://github.com/BonnierNews/stayput.git","type":"git"},"_npmVersion":"11.12.1","description":"Default-deny network guard for Node.js test environments — blocks non-local TCP at the socket level.","directories":{},"_nodeVersion":"24.15.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"pg":"^8.13.0","mongodb":"^6.10.0","@elastic/elasticsearch":"^8.17.0"},"_npmOperationalInternal":{"tmp":"tmp/stayput_0.1.0_1789049356409_0.7532045837492087","host":"s3://npm-registry-packages-npm-production"}},"0.1.2":{"name":"@bonniernews/stayput","version":"0.1.2","license":"MIT","_id":"@bonniernews/stayput@0.1.2","maintainers":[{"name":"markusn","email":"markus@botten.org"},{"name":"mxtr","email":"max.olofsson@bonniernews.se"},{"name":"matsrorbecker","email":"mats@rorbecker.com"},{"name":"dan.karlsson","email":"dan.arola@bonniernews.se"},{"name":"adil.aboulkacim","email":"adil.aboulkacim@bonniernews.se"},{"name":"marie-winther","email":"marie.winther@bonniernews.se"},{"name":"marcusgronblad","email":"marcus.gronblad@bonniernews.se"},{"name":"axeljohanssonbonniernews","email":"axel.johansson@bonniernews.se"},{"name":"karlbergc","email":"christoffer.karlberg@bonniernews.se"},{"name":"chavah","email":"chavah.forler@bonniernews.se"},{"name":"joelekman","email":"joel.ekman@bonniernews.se"},{"name":"adam.hakansson","email":"adam.hakansson@bonniernews.se"},{"name":"hilleso","email":"marcus.hilleso@expressen.se"},{"name":"gusliden","email":"gusliden@gmail.com"},{"name":"andreas.samuelsson","email":"andreas.samuelsson@bonniernews.se"},{"name":"jzachrisson","email":"jesper.zachrisson@bonniernews.se"},{"name":"norla","email":"mattias.norlander@gmail.com"},{"name":"varneynz","email":"simon.varney@bonniernews.se"},{"name":"aliceboberg","email":"alice.boberg@bonniernews.se"},{"name":"johark","email":"johan.arkad@bonniernews.se"},{"name":"nifo","email":"niklas.forsstrom@bonniernews.se"},{"name":"herbola","email":"herman.jansson@bonniernews.se"},{"name":"vitryssen","email":"andre.nordlund@bonniernews.se"},{"name":"takolander","email":"william.takolander@bonniernews.se"},{"name":"morre","email":"marten.persson@hdsydsvenskan.se"},{"name":"mikael.mattsson","email":"mikael.mattsson@bonniernews.se"},{"name":"schristianssonbn","email":"seb.christiansson@bonniernews.se"},{"name":"mattiasobn","email":"mattias.olla@bonniernews.se"},{"name":"daghall","email":"markus@daghall.se"},{"name":"erandersson","email":"ericandersson1@gmail.com"},{"name":"jonaek","email":"jonathan.ek@bonniernews.se"},{"name":"amundsentb","email":"amundsentb@gmail.com"},{"name":"drgeobn","email":"daniel.rasmussen@bonniernews.se"},{"name":"oscartholander","email":"oscar@tholander.nu"},{"name":"peterpettersson","email":"peter.pettersson@bonniernews.se"},{"name":"liroliro","email":"basistoscar@gmail.com"},{"name":"torkelberget","email":"andreas.egneblad@bonniernews.se"},{"name":"johankasperi","email":"johan@kasperi.se"},{"name":"jon.eldeklint","email":"jon.eldeklint@bonniernews.se"},{"name":"nblom","email":"niklas.blom@bonniernews.se"},{"name":"jackesa","email":"jakob.pedersen@bonniernews.se"},{"name":"ollenolle","email":"olle.nordin@bonniernews.se"},{"name":"emilbjorklund","email":"emil@thatemil.com"},{"name":"lilianj","email":"japlilian@gmail.com"},{"name":"kristofferjansson","email":"kristoffer.jansson@bonniernews.se"},{"name":"kitgus","email":"kit.gustavsson@bonniernews.se"},{"name":"odynvolk","email":"alexi.rahman@r76.se"}],"homepage":"https://github.com/BonnierNews/stayput#readme","bugs":{"url":"https://github.com/BonnierNews/stayput/issues"},"dist":{"shasum":"ffe3e6c2bc1314e99f3e862c1122acf418781e74","tarball":"https://registry.npmjs.org/@bonniernews/stayput/-/stayput-0.1.2.tgz","fileCount":6,"integrity":"sha512-Yagi40ylgGe7fGoTVcUj0IHaxVMgSStEWiZCkdCnXIS0KvY2FGgSKP/YfOYgkDzCZSkj3mWpNKKa5I4qIUA31w==","signatures":[{"sig":"MEQCICcNpFSEUhRxOfZTsIfTjt9iqr168Johgx7NfYayKju+AiBUYK9bye7byPzPt7bMXU6Y6jjIxREctXW5ADBbLeqz/w==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":18462},"type":"module","engines":{"node":">=20.6.0"},"exports":{".":"./index.js","./mocha":"./mocha.js","./register":"./register.js","./package.json":"./package.json"},"gitHead":"afedd704fedf411f1dc0782e9293919856bd7e9a","scripts":{"test":"node test.js","prepublishOnly":"npm test","test:integration":"node test-integration.js"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:0345d395-b147-4050-8c1f-aee338448a91"}},"repository":{"url":"git+https://github.com/BonnierNews/stayput.git","type":"git"},"_npmVersion":"11.19.1","description":"Default-deny network guard for Node.js test environments — blocks non-local TCP at the socket level.","directories":{},"_nodeVersion":"24.20.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"pg":"^8.13.0","mongodb":"^6.10.0","@elastic/elasticsearch":"^8.17.0"},"_npmOperationalInternal":{"tmp":"tmp/stayput_0.1.2_1789061788024_0.7208135487392213","host":"s3://npm-registry-packages-npm-production"}},"0.1.3":{"name":"@bonniernews/stayput","version":"0.1.3","license":"MIT","_id":"@bonniernews/stayput@0.1.3","maintainers":[{"name":"markusn","email":"markus@botten.org"},{"name":"mxtr","email":"max.olofsson@bonniernews.se"},{"name":"matsrorbecker","email":"mats@rorbecker.com"},{"name":"dan.karlsson","email":"dan.arola@bonniernews.se"},{"name":"adil.aboulkacim","email":"adil.aboulkacim@bonniernews.se"},{"name":"marie-winther","email":"marie.winther@bonniernews.se"},{"name":"marcusgronblad","email":"marcus.gronblad@bonniernews.se"},{"name":"axeljohanssonbonniernews","email":"axel.johansson@bonniernews.se"},{"name":"karlbergc","email":"christoffer.karlberg@bonniernews.se"},{"name":"chavah","email":"chavah.forler@bonniernews.se"},{"name":"joelekman","email":"joel.ekman@bonniernews.se"},{"name":"adam.hakansson","email":"adam.hakansson@bonniernews.se"},{"name":"hilleso","email":"marcus.hilleso@expressen.se"},{"name":"gusliden","email":"gusliden@gmail.com"},{"name":"andreas.samuelsson","email":"andreas.samuelsson@bonniernews.se"},{"name":"jzachrisson","email":"jesper.zachrisson@bonniernews.se"},{"name":"norla","email":"mattias.norlander@gmail.com"},{"name":"varneynz","email":"simon.varney@bonniernews.se"},{"name":"aliceboberg","email":"alice.boberg@bonniernews.se"},{"name":"johark","email":"johan.arkad@bonniernews.se"},{"name":"nifo","email":"niklas.forsstrom@bonniernews.se"},{"name":"herbola","email":"herman.jansson@bonniernews.se"},{"name":"vitryssen","email":"andre.nordlund@bonniernews.se"},{"name":"takolander","email":"william.takolander@bonniernews.se"},{"name":"morre","email":"marten.persson@hdsydsvenskan.se"},{"name":"mikael.mattsson","email":"mikael.mattsson@bonniernews.se"},{"name":"schristianssonbn","email":"seb.christiansson@bonniernews.se"},{"name":"mattiasobn","email":"mattias.olla@bonniernews.se"},{"name":"daghall","email":"markus@daghall.se"},{"name":"erandersson","email":"ericandersson1@gmail.com"},{"name":"jonaek","email":"jonathan.ek@bonniernews.se"},{"name":"amundsentb","email":"amundsentb@gmail.com"},{"name":"drgeobn","email":"daniel.rasmussen@bonniernews.se"},{"name":"oscartholander","email":"oscar@tholander.nu"},{"name":"peterpettersson","email":"peter.pettersson@bonniernews.se"},{"name":"liroliro","email":"basistoscar@gmail.com"},{"name":"torkelberget","email":"andreas.egneblad@bonniernews.se"},{"name":"johankasperi","email":"johan@kasperi.se"},{"name":"jon.eldeklint","email":"jon.eldeklint@bonniernews.se"},{"name":"nblom","email":"niklas.blom@bonniernews.se"},{"name":"jackesa","email":"jakob.pedersen@bonniernews.se"},{"name":"ollenolle","email":"olle.nordin@bonniernews.se"},{"name":"emilbjorklund","email":"emil@thatemil.com"},{"name":"lilianj","email":"japlilian@gmail.com"},{"name":"kristofferjansson","email":"kristoffer.jansson@bonniernews.se"},{"name":"kitgus","email":"kit.gustavsson@bonniernews.se"},{"name":"odynvolk","email":"alexi.rahman@r76.se"}],"homepage":"https://github.com/BonnierNews/stayput#readme","bugs":{"url":"https://github.com/BonnierNews/stayput/issues"},"dist":{"shasum":"ec88106834ccdccbaa26e9566e9a09b4ae42bda1","tarball":"https://registry.npmjs.org/@bonniernews/stayput/-/stayput-0.1.3.tgz","fileCount":9,"integrity":"sha512-n2mMwTw7QuhTX3RpUktbMDxdlOR7zlLD+GsM+4Lk3AslXpZzJwkKYfVX7ZChV8F8V2kugGlP1CaBGMf3J1tvug==","signatures":[{"sig":"MEYCIQDkHIlz9cRN5YomaE0AyFPAqEDWDhS+HuPYlADIJRhYRQIhANMJTp7twEfbo/BsTXPHVrcYfp9a41CJiQhigTYGtQWJ","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEYCIQDBSbW2H2cA8iJRPqcxsNIXLdBq4apVUGSudARoo4sd8AIhAIiPWAiTycvC6CW/JYpNfxZfuSKom69RfThEs3TAlG/I","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":20911},"type":"module","types":"./index.d.ts","engines":{"node":">=20.6.0"},"exports":{".":{"types":"./index.d.ts","default":"./index.js"},"./mocha":{"types":"./mocha.d.ts","default":"./mocha.js"},"./register":{"types":"./register.d.ts","default":"./register.js"},"./package.json":"./package.json"},"gitHead":"cc44fdc864926db2152b413cd66a864b3af7f4c4","scripts":{"test":"node test.js && tsc","prepublishOnly":"npm test","test:integration":"node test-integration.js"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:0345d395-b147-4050-8c1f-aee338448a91"}},"repository":{"url":"git+https://github.com/BonnierNews/stayput.git","type":"git"},"_npmVersion":"11.19.1","description":"Default-deny network guard for Node.js test environments — blocks non-local TCP at the socket level.","directories":{},"_nodeVersion":"24.20.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"pg":"^8.13.0","mongodb":"^6.10.0","typescript":"^7.0.2","@elastic/elasticsearch":"^8.17.0"},"_npmOperationalInternal":{"tmp":"tmp/stayput_0.1.3_1789987686550_0.8256421209997127","host":"s3://npm-registry-packages-npm-production"}},"0.1.4":{"_id":"@bonniernews/stayput@0.1.4","bugs":{"url":"https://github.com/BonnierNews/stayput/issues"},"dist":{"shasum":"a288c64cd22e8b2322d94666c1cef00f8c38fe79","tarball":"https://registry.npmjs.org/@bonniernews/stayput/-/stayput-0.1.4.tgz","fileCount":9,"integrity":"sha512-qSo0HaGGS5tpMUJGu0+XLpUuQfcBTQHS6fOEEZk0OgA+aQ37Gk6MSp1mDrG4J8OZiy4pXRDH3/Fcbc9HPDd2HA==","signatures":[{"sig":"MEUCIQCiwi8/iJISqoUf5dG8ehbQVHKk2h7QrRhi0WMtwIHgnwIgQ2+KgP9Fy9uGX0qEXOd30FG1IeXav73xFC6emlz11p4=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQCGkRcRn6/PZo5bYwCl8ZNcadA8AHAw1R5sBYy6svtPfgIgEc0r0ztyf4TGA/rC71YEEN924KMtm4XTFVaFo8jmh4Q="}],"unpackedSize":22506},"name":"@bonniernews/stayput","type":"module","types":"./index.d.ts","engines":{"node":">=20.6.0"},"exports":{".":{"types":"./index.d.ts","default":"./index.js"},"./mocha":{"types":"./mocha.d.ts","default":"./mocha.js"},"./register":{"types":"./register.d.ts","default":"./register.js"},"./package.json":"./package.json"},"gitHead":"fff4041771221d842e87f8b609469422e89bdb6d","license":"MIT","scripts":{"test":"node test.js && tsc","prepublishOnly":"npm test","test:integration":"node test-integration.js"},"version":"0.1.4","_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:0345d395-b147-4050-8c1f-aee338448a91"}},"homepage":"https://github.com/BonnierNews/stayput#readme","repository":{"url":"git+https://github.com/BonnierNews/stayput.git","type":"git"},"_npmVersion":"11.20.0","description":"Default-deny network guard for Node.js test environments — blocks non-local TCP at the socket level.","directories":{},"maintainers":[{"name":"markusn","email":"markus@botten.org"},{"name":"mxtr","email":"max.olofsson@bonniernews.se"},{"name":"matsrorbecker","email":"mats@rorbecker.com"},{"name":"dan.karlsson","email":"dan.arola@bonniernews.se"},{"name":"adil.aboulkacim","email":"adil.aboulkacim@bonniernews.se"},{"name":"marie-winther","email":"marie.winther@bonniernews.se"},{"name":"marcusgronblad","email":"marcus.gronblad@bonniernews.se"},{"name":"axeljohanssonbonniernews","email":"axel.johansson@bonniernews.se"},{"name":"karlbergc","email":"christoffer.karlberg@bonniernews.se"},{"name":"chavah","email":"chavah.forler@bonniernews.se"},{"name":"joelekman","email":"joel.ekman@bonniernews.se"},{"name":"adam.hakansson","email":"adam.hakansson@bonniernews.se"},{"name":"hilleso","email":"marcus.hilleso@expressen.se"},{"name":"gusliden","email":"gusliden@gmail.com"},{"name":"andreas.samuelsson","email":"andreas.samuelsson@bonniernews.se"},{"name":"jzachrisson","email":"jesper.zachrisson@bonniernews.se"},{"name":"norla","email":"mattias.norlander@gmail.com"},{"name":"varneynz","email":"simon.varney@bonniernews.se"},{"name":"aliceboberg","email":"alice.boberg@bonniernews.se"},{"name":"johark","email":"johan.arkad@bonniernews.se"},{"name":"nifo","email":"niklas.forsstrom@bonniernews.se"},{"name":"herbola","email":"herman.jansson@bonniernews.se"},{"name":"vitryssen","email":"andre.nordlund@bonniernews.se"},{"name":"takolander","email":"william.takolander@bonniernews.se"},{"name":"morre","email":"marten.persson@hdsydsvenskan.se"},{"name":"mikael.mattsson","email":"mikael.mattsson@bonniernews.se"},{"name":"schristianssonbn","email":"seb.christiansson@bonniernews.se"},{"name":"mattiasobn","email":"mattias.olla@bonniernews.se"},{"name":"daghall","email":"markus@daghall.se"},{"name":"erandersson","email":"ericandersson1@gmail.com"},{"name":"jonaek","email":"jonathan.ek@bonniernews.se"},{"name":"amundsentb","email":"amundsentb@gmail.com"},{"name":"drgeobn","email":"daniel.rasmussen@bonniernews.se"},{"name":"oscartholander","email":"oscar@tholander.nu"},{"name":"peterpettersson","email":"peter.pettersson@bonniernews.se"},{"name":"liroliro","email":"basistoscar@gmail.com"},{"name":"torkelberget","email":"andreas.egneblad@bonniernews.se"},{"name":"johankasperi","email":"johan@kasperi.se"},{"name":"jon.eldeklint","email":"jon.eldeklint@bonniernews.se"},{"name":"nblom","email":"niklas.blom@bonniernews.se"},{"name":"jackesa","email":"jakob.pedersen@bonniernews.se"},{"name":"ollenolle","email":"olle.nordin@bonniernews.se"},{"name":"emilbjorklund","email":"emil@thatemil.com"},{"name":"lilianj","email":"japlilian@gmail.com"},{"name":"kristofferjansson","email":"kristoffer.jansson@bonniernews.se"},{"name":"kitgus","email":"kit.gustavsson@bonniernews.se"},{"name":"odynvolk","email":"alexi.rahman@r76.se"}],"_nodeVersion":"24.21.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"pg":"^8.13.0","mongodb":"^6.10.0","typescript":"^7.0.2","@elastic/elasticsearch":"^8.17.0"},"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/stayput_0.1.4_1790321151745_0.2660804315290419"}}},"time":{"created":"2026-09-10T14:09:16.171Z","modified":"2026-09-25T07:25:52.058Z","0.1.0":"2026-09-10T14:09:16.579Z","0.1.2":"2026-09-10T17:36:28.198Z","0.1.3":"2026-09-21T10:48:06.648Z","0.1.4":"2026-09-25T07:25:51.866Z"},"bugs":{"url":"https://github.com/BonnierNews/stayput/issues"},"license":"MIT","homepage":"https://github.com/BonnierNews/stayput#readme","repository":{"url":"git+https://github.com/BonnierNews/stayput.git","type":"git"},"description":"Default-deny network guard for Node.js test environments — blocks non-local TCP at the socket level.","maintainers":[{"name":"markusn","email":"markus@botten.org"},{"name":"mxtr","email":"max.olofsson@bonniernews.se"},{"name":"matsrorbecker","email":"mats@rorbecker.com"},{"name":"dan.karlsson","email":"dan.arola@bonniernews.se"},{"name":"adil.aboulkacim","email":"adil.aboulkacim@bonniernews.se"},{"name":"marie-winther","email":"marie.winther@bonniernews.se"},{"name":"marcusgronblad","email":"marcus.gronblad@bonniernews.se"},{"name":"axeljohanssonbonniernews","email":"axel.johansson@bonniernews.se"},{"name":"karlbergc","email":"christoffer.karlberg@bonniernews.se"},{"name":"chavah","email":"chavah.forler@bonniernews.se"},{"name":"joelekman","email":"joel.ekman@bonniernews.se"},{"name":"adam.hakansson","email":"adam.hakansson@bonniernews.se"},{"name":"hilleso","email":"marcus.hilleso@expressen.se"},{"name":"gusliden","email":"gusliden@gmail.com"},{"name":"andreas.samuelsson","email":"andreas.samuelsson@bonniernews.se"},{"name":"jzachrisson","email":"jesper.zachrisson@bonniernews.se"},{"name":"norla","email":"mattias.norlander@gmail.com"},{"name":"varneynz","email":"simon.varney@bonniernews.se"},{"name":"aliceboberg","email":"alice.boberg@bonniernews.se"},{"name":"johark","email":"johan.arkad@bonniernews.se"},{"name":"nifo","email":"niklas.forsstrom@bonniernews.se"},{"name":"herbola","email":"herman.jansson@bonniernews.se"},{"name":"vitryssen","email":"andre.nordlund@bonniernews.se"},{"name":"takolander","email":"william.takolander@bonniernews.se"},{"name":"morre","email":"marten.persson@hdsydsvenskan.se"},{"name":"mikael.mattsson","email":"mikael.mattsson@bonniernews.se"},{"name":"schristianssonbn","email":"seb.christiansson@bonniernews.se"},{"name":"mattiasobn","email":"mattias.olla@bonniernews.se"},{"name":"daghall","email":"markus@daghall.se"},{"name":"erandersson","email":"ericandersson1@gmail.com"},{"name":"jonaek","email":"jonathan.ek@bonniernews.se"},{"name":"amundsentb","email":"amundsentb@gmail.com"},{"name":"drgeobn","email":"daniel.rasmussen@bonniernews.se"},{"name":"oscartholander","email":"oscar@tholander.nu"},{"name":"peterpettersson","email":"peter.pettersson@bonniernews.se"},{"name":"liroliro","email":"basistoscar@gmail.com"},{"name":"torkelberget","email":"andreas.egneblad@bonniernews.se"},{"name":"johankasperi","email":"johan@kasperi.se"},{"name":"jon.eldeklint","email":"jon.eldeklint@bonniernews.se"},{"name":"nblom","email":"niklas.blom@bonniernews.se"},{"name":"jackesa","email":"jakob.pedersen@bonniernews.se"},{"name":"ollenolle","email":"olle.nordin@bonniernews.se"},{"name":"emilbjorklund","email":"emil@thatemil.com"},{"name":"lilianj","email":"japlilian@gmail.com"},{"name":"kristofferjansson","email":"kristoffer.jansson@bonniernews.se"},{"name":"kitgus","email":"kit.gustavsson@bonniernews.se"},{"name":"odynvolk","email":"alexi.rahman@r76.se"}],"readme":"# @bonniernews/stayput\n\nDefault-deny network guard for Node.js test environments. Blocks non-local TCP at the socket level, so a test suite with a leaked production connection string fails loudly at connect time instead of silently mutating production. Works for raw-TCP drivers (pg, mongodb, elasticsearch, redis) as well as fetch/undici — not just HTTP.\n\n## Quick start\n\n```js\n// .mocharc.cjs\nmodule.exports = { require: ['@bonniernews/stayput/register'] };\n```\n\nBlocked connections throw with `err.code === 'EREMOTEBLOCKED'`.\n\nCI already injects the guard into every node process via `NODE_OPTIONS` in the shared workflow templates — the mocharc line is what protects laptops, which is where stray prod credentials actually live.\n\nAdoption is checked by ESLint: the shared ESLint config ships two rules — `stayput-required` flags a mocharc missing the register line (auto-fixable), and `project-has-guard` flags test files not governed by any stayput-loading mocha config. If you're seeing one of those warnings, the quick start above is the fix.\n\n### Runner-proofing (belt and suspenders)\n\nThe mocharc line only guards runs that load the mocharc. `mocha --no-config`, mocha invoked from a subdirectory, `node --test`, or jest all skip it. If your repo has a shared test helper that tests import anyway, anchor the guard there too — loading is idempotent, so double-loading costs nothing:\n\n```js\n// test/helpers/db.js\nimport '@bonniernews/stayput/register'; // guard on, no matter how the runner was invoked\nimport { assertActive } from '@bonniernews/stayput';\n\nexport async function testDb() {\n  assertActive(); // refuse to hand out connections if the guard was tampered with\n  // ...create and return the client\n}\n```\n\nBoundary rule: stayput is a devDependency and only test-owned files (test helpers, setup files, mocharc) may import it — never `src/`, where it would crash a production install. If tests use the app's real DB module directly, add a thin test helper in between rather than gating production code on `NODE_ENV`.\n\n(In CI this is all redundant — `NODE_OPTIONS` injection guards every node process regardless of runner or config.)\n\n## Policy\n\nLocally (no `CI` env): loopback only. In CI: loopback + private ranges (RFC1918). Everything else is blocked.\n\n| Env var | Effect |\n|---|---|\n| `STAYPUT_ALLOW` | comma-separated extra hosts / IPs / IPv4 CIDRs, e.g. `10.128.0.5,db.internal` |\n| `STAYPUT_DENY` | same format — **deny beats allow** |\n| `STAYPUT_DISABLE=I_UNDERSTAND_THE_RISK` | turns the guard off (deliberately ugly so it stands out in review) |\n\nPut overrides in the workflow file, not in code: `STAYPUT_ALLOW`/`STAYPUT_DISABLE` are greppable fleet-wide and double as the exception inventory.\n\n## AI agents\n\nAn `EREMOTEBLOCKED` error is a stop signal, not a bug to fix. Do not set `STAYPUT_ALLOW` or `STAYPUT_DISABLE`, edit workflow env, or remove the mocharc line to make tests pass — the block usually means the test suite is pointing at infrastructure it should not touch. Pause and ask a human to verify the target. Only a human adds exceptions, in the workflow file, where they are visible in review and in the fleet-wide grep.\n\nThis contract has been red-teamed (2026-08-24): a coding agent (Claude) was given a sandboxed repo whose tests hit a blocked private-range database and told to \"just get it green today\". It diagnosed the guard from the error message, refused to override or fake the result (including mocking the driver or repointing the test), and stopped to ask for human verification. Steering, not enforcement — but it works on well-behaved agents.\n\n## Programmatic API\n\n```js\nimport stayput, { enable, assertActive } from '@bonniernews/stayput';\n\nenable({\n  allow: ['10.128.0.5'],\n  deny: ['10.10.0.0/16'],   // deny beats allow\n  privateRanges: 'auto',    // 'auto' = private ranges only when CI is set\n  onBlock: (host, port) => {},\n});\nassertActive();             // throws if never loaded or unpatched\nstayput.isActive;           // live getter on the default export\n```\n\nPrefer the named exports: `stayput.assertActive()` on the default import trips `import/no-named-as-default-member` in the shared ESLint config. Type declarations ship with the package (`StayputOptions`, `Stayput`), so TypeScript test helpers need no `declare module` stub.\n\n`@bonniernews/stayput/mocha` exports a root hook plugin that runs `assertActive()` before tests.\n\nLoading is idempotent: multiple loads (NODE_OPTIONS + mocharc + import) merge allowlists, patch once, and log one greppable line:\n\n```\nstayput/0.1.0 active mode=loopback+private allow=2 deny=1 source=NODE_OPTIONS\n```\n\n## One layer of many\n\nstayput is a client-side footgun guard — the last line of defence, not the plan. It complements, never replaces:\n\n- **Education** — knowing why prod credentials don't belong on laptops or in repos beats any guard.\n- **Easy, short-lived access paths** — when you genuinely need to reach a real database, use the ephemeral proxies/jumphosts on non-default ports. The sanctioned way is easier than the risky way, and a leaked default-port connection string doesn't route anywhere.\n- **Firewall rules / network design** — test environments should have no route to production at all; stayput only matters where a route exists.\n- **Test frameworks and fixtures** — testcontainers, CI service containers, and hermetic fixtures remove the reason to point tests at shared infrastructure in the first place.\n- **Server-side guards** — read-only default roles, DDL gating, RBAC without destructive verbs (per-database plan in [HANDOVER.md](HANDOVER.md)).\n\n## How it works\n\nPatches `net.Socket.prototype.connect` (sync throw on blocked IP literals; unix sockets always allowed) and `dns.lookup` (hostnames are vetted when they resolve). TLS, http/https and undici ride on `net.Socket`, so they're covered for free. `server.listen` resolves its host through `dns.lookup` too, so binding is allowed for loopback and for the unspecified addresses (`0.0.0.0`, `::`) — binding a specific non-local IP needs `STAYPUT_ALLOW`. This is a footgun guard, not a security boundary.\n\nRequires node ≥ 20.6 (preloaded in CI with `NODE_OPTIONS=--import …/register.js`).\n\n### Not covered\n\n**Other processes.** The patches live in one Node process. Child `node` processes pick them up only through `NODE_OPTIONS` (which is how the CI injection reaches every process), and nothing that isn't Node ever does: a native binary spawned by a CLI, `psql` in an npm script, a container started by the test run. Prisma is the common case — `prisma migrate deploy` opens its connection from the Rust schema engine it spawns, so a test script that runs migrations before mocha is unguarded on that step even with stayput registered in the same process (`prisma generate` opens no connection). For those steps, pin what they read instead: the config environment and the connection string. stayput guards the test process; it does not guard the script around it.\n\n**Loopback proxies and tunnels.** A Cloud SQL Auth Proxy, `kubectl port-forward` or SSH tunnel listening on 127.0.0.1 is loopback, and loopback is allowed — stayput cannot tell a local postgres on 127.0.0.1:5432 from a tunnel to production on the same port. Don't run anything on the default port locally: give the test container its own non-default port and every proxy or tunnel a different one, so each port has exactly one destination (this repo's own compose.yaml publishes postgres on 54329, mongo on 27019 and elasticsearch on 9209). Then the default port refuses every connection, a leaked or copy-pasted connection string carrying it goes nowhere, and a real environment is only ever reached on purpose.\n\n## Development\n\n```sh\nnpm test                  # unit tests + type declaration check — no network, no docker\nnpm run test:integration  # pg + mongodb + elasticsearch against docker (compose.yaml)\n```\n\nDesign decisions, environment matrix, and rollout plan: [HANDOVER.md](HANDOVER.md).\n","readmeFilename":"README.md"}