{"_id":"secure-library-loader","_rev":"4-8e2da7f8d977614f373fd67d8be11db7","name":"secure-library-loader","dist-tags":{"latest":"0.1.3"},"versions":{"0.1.0":{"name":"secure-library-loader","version":"0.1.0","keywords":["encryption","aes-256-gcm","scrypt","library","loader"],"author":{"name":"Robert Johnson"},"license":"MIT","_id":"secure-library-loader@0.1.0","maintainers":[{"name":"robert92","email":"robertjohnson8601@proton.me"}],"bin":{"secure-library-loader":"bin/cli.js"},"dist":{"shasum":"c978726e28851540fd9ccd83ff030d8a5f172627","tarball":"https://registry.npmjs.org/secure-library-loader/-/secure-library-loader-0.1.0.tgz","fileCount":10,"integrity":"sha512-3rVRsFtcRnmQ1H8Nmsnq78gz7UDaofZYimt7zNxwSJXMfRPA1kygneC3f2dwkGsdbUnIbVI7GT/X/WUrBZWf9g==","signatures":[{"sig":"MEUCIFnHBnCoaoCALewrzkVB6V4aczOYSjW/6j4zRaSYdlPcAiEA5iiCWk6j+AM4YPXEHmAjZtrYRICYZDVdkBIVQA2YukA=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":38761},"main":"src/index.js","type":"commonjs","engines":{"node":">=18"},"scripts":{"test":"node --test test/archive.test.js test/crypto.test.js test/index.test.js test/loader.test.js","release":"npm publish --access public","pack:check":"npm pack --dry-run","publish:dry":"npm publish --dry-run","prepublishOnly":"npm test"},"_npmUser":{"name":"robert92","email":"robertjohnson8601@proton.me"},"_npmVersion":"11.12.1","description":"Encrypt a library's source into a single portable, password-protected file, then decrypt, install, and load its modules at runtime.","directories":{},"_nodeVersion":"24.15.0","_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/secure-library-loader_0.1.0_1787653667020_0.08470883641224503","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"secure-library-loader","version":"0.1.1","keywords":["encryption","aes-256-gcm","scrypt","library","loader"],"author":{"name":"Robert Johnson"},"license":"MIT","_id":"secure-library-loader@0.1.1","maintainers":[{"name":"robert92","email":"robertjohnson8601@proton.me"}],"bin":{"secure-library-loader":"bin/cli.js"},"dist":{"shasum":"4ff99172f441c9006e48904c2928ce40cc781dbe","tarball":"https://registry.npmjs.org/secure-library-loader/-/secure-library-loader-0.1.1.tgz","fileCount":10,"integrity":"sha512-bABe47Sdcp0lAdNu8qzInXydUTUX8ygzTo/2TdMS8O9voBQSFW38E6Cqlso2Jb7CntAr5cCaboSgn8jUtwBp9Q==","signatures":[{"sig":"MEYCIQCmepceQPxBilJvFJLRr0XP8OnkYSvdABnYzdjJ6046KQIhAP1uSXvyCAcwlV03Xbymvs8Z6ztj9jQ9nJU3koSTWD6x","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":39705},"main":"src/index.js","type":"commonjs","engines":{"node":">=18"},"scripts":{"test":"node --test test/archive.test.js test/crypto.test.js test/index.test.js test/loader.test.js","release":"npm publish --access public","pack:check":"npm pack --dry-run","publish:dry":"npm publish --dry-run","prepublishOnly":"npm test"},"_npmUser":{"name":"robert92","email":"robertjohnson8601@proton.me"},"_npmVersion":"11.12.1","description":"Encrypt a library's source into a single portable, password-protected file, then decrypt, install, and load its modules at runtime.","directories":{},"_nodeVersion":"24.15.0","_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/secure-library-loader_0.1.1_1787659147350_0.1356598127157269","host":"s3://npm-registry-packages-npm-production"}},"0.1.2":{"name":"secure-library-loader","version":"0.1.2","keywords":["encryption","aes-256-gcm","scrypt","library","loader"],"author":{"name":"Robert Johnson"},"license":"MIT","_id":"secure-library-loader@0.1.2","maintainers":[{"name":"robert92","email":"robertjohnson8601@proton.me"}],"bin":{"secure-library-loader":"bin/cli.js"},"dist":{"shasum":"87ae66294de3768e16fb14a539b25ea56ac6c966","tarball":"https://registry.npmjs.org/secure-library-loader/-/secure-library-loader-0.1.2.tgz","fileCount":10,"integrity":"sha512-bHHcP51j8sTp5MTGHgaMKQYGIZmvQyKLtw0fvGzLDfxfk5WGWaSNwtopzLTp/BEnb0PAbUljrIXfegqXt49MlQ==","signatures":[{"sig":"MEQCIEGXZhR/qpGL/KMdqaXQcWKUEJ+rKU9N0RHB98OvKfjdAiBqP+2G3KOLSNr5NGpXzQsHg083Tt8ukYb7IuQCgfYr+g==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":42943},"main":"src/index.js","type":"commonjs","engines":{"node":">=18"},"scripts":{"test":"node --test test/archive.test.js test/crypto.test.js test/index.test.js test/loader.test.js","release":"npm publish --access public","pack:check":"npm pack --dry-run","publish:dry":"npm publish --dry-run","prepublishOnly":"npm test"},"_npmUser":{"name":"robert92","email":"robertjohnson8601@proton.me"},"_npmVersion":"11.12.1","description":"Encrypt a library's source into a single portable, password-protected file, then decrypt, install, and load its modules at runtime.","directories":{},"_nodeVersion":"24.15.0","_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/secure-library-loader_0.1.2_1787663717360_0.8234749219021256","host":"s3://npm-registry-packages-npm-production"}},"0.1.3":{"name":"secure-library-loader","version":"0.1.3","description":"Encrypt a library's source into a single portable, password-protected file, then decrypt, install, and load its modules at runtime.","main":"src/index.js","bin":{"secure-library-loader":"bin/cli.js"},"type":"commonjs","engines":{"node":">=18"},"scripts":{"test":"node --test test/archive.test.js test/crypto.test.js test/index.test.js test/loader.test.js","prepublishOnly":"npm test","pack:check":"npm pack --dry-run","publish:dry":"npm publish --dry-run","release":"npm publish --access public"},"keywords":["encryption","aes-256-gcm","scrypt","library","loader"],"license":"MIT","author":{"name":"Robert Johnson"},"_id":"secure-library-loader@0.1.3","_nodeVersion":"24.15.0","_npmVersion":"11.10.1","dist":{"integrity":"sha512-Zy+ic1PIRcxyZ2BJbT3KB0Nw6Y1YSVtDB4FuySw5CEEPLdlDXkRKwwltkY/YTIvGsv7iVG6GzLnPph+4rmajYg==","shasum":"dfb121e58648ff7cd0062365fe5b05594c069902","tarball":"https://registry.npmjs.org/secure-library-loader/-/secure-library-loader-0.1.3.tgz","fileCount":10,"unpackedSize":49150,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQCnQnX76OKoOjhhbs7xiHpKmKvnmapjGKfYSS9rVdkfdQIgcDMcypeduGqxoICe3nKyVC5u7D4DIzUkMaoBBaSm3Mg="}]},"_npmUser":{"name":"robert92","email":"robertjohnson8601@proton.me"},"directories":{},"maintainers":[{"name":"robert92","email":"robertjohnson8601@proton.me"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/secure-library-loader_0.1.3_1788161463199_0.25074390833640314"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-25T10:27:46.843Z","modified":"2026-08-31T07:31:03.530Z","0.1.0":"2026-08-25T10:27:47.174Z","0.1.1":"2026-08-25T11:59:07.505Z","0.1.2":"2026-08-25T13:15:17.499Z","0.1.3":"2026-08-31T07:31:03.345Z"},"author":{"name":"Robert Johnson"},"license":"MIT","keywords":["encryption","aes-256-gcm","scrypt","library","loader"],"description":"Encrypt a library's source into a single portable, password-protected file, then decrypt, install, and load its modules at runtime.","maintainers":[{"name":"robert92","email":"robertjohnson8601@proton.me"}],"readme":"# secure-library-loader\r\n\r\nEncrypt a library's source directory into a single portable file, and decrypt\r\nit back to disk with a password. Built on Node's native `crypto` module —\r\n**zero runtime dependencies**.\r\n\r\n## What it does\r\n\r\n- **Encrypt**: packs every file under a source directory into a single\r\n  archive, compresses it, and encrypts it with a password using\r\n  `scrypt` (key derivation) + `AES-256-GCM` (authenticated encryption).\r\n- **Decrypt**: given the encrypted file and the correct password, restores\r\n  the original directory tree exactly.\r\n- **Install & load**: decrypts an encrypted library into your module's\r\n  `lib/` folder, then hands you loaded module exports via\r\n  `getModuleByName(\"name\")`.\r\n\r\nThe output is a single self-describing file. It stores everything needed to\r\ndecrypt it (KDF parameters, salt, IV, auth tag) *except* the password, which\r\nyou must supply yourself.\r\n\r\n## What it does *not* do\r\n\r\nThis is encryption, not code obfuscation or DRM. If someone has both the\r\nencrypted file and the password, they can decrypt it and read the source —\r\nsame as any password-protected zip. Use this to protect source code at rest\r\nor in transit (e.g. in a private artifact store, over email, in a CI\r\nartifact), not to prevent a licensed end user from ever seeing your code.\r\n\r\n## Install\r\n\r\n```bash\r\nnpm install secure-library-loader\r\n```\r\n\r\nFor global CLI use:\r\n\r\n```bash\r\nnpm install -g secure-library-loader\r\n```\r\n\r\n## Password handling\r\n\r\nThe password is **never** accepted as a command-line argument (that would\r\nleak into shell history and process listings). It's resolved in this order:\r\n\r\n1. An explicit `password` option passed to the JS API\r\n2. The `SLL_PASSWORD` environment variable\r\n3. An interactive, echo-disabled terminal prompt (async APIs only)\r\n\r\nSynchronous APIs (`encryptLibrarySync`, `decryptLibrarySync`,\r\n`installLibrarySync`, `loadEncryptedModuleSync`, `listModulesSync`) skip\r\nthe prompt: they require `password` or `SLL_PASSWORD`. The async names\r\nexist mainly so the CLI and JS API can prompt on a TTY; the encrypt /\r\ndecrypt / install work itself is synchronous.\r\n\r\n## CLI usage\r\n\r\n### Encrypt a directory\r\n\r\n```bash\r\nsecure-library-loader encrypt ./my-lib -o my-lib.sll\r\n```\r\n\r\nYou'll be prompted for a password (or set `SLL_PASSWORD` beforehand).\r\n\r\n### Decrypt back to a directory\r\n\r\n```bash\r\nsecure-library-loader decrypt my-lib.sll -o ./restored-lib\r\n```\r\n\r\nAdd `--force` to overwrite a non-empty output directory:\r\n\r\n```bash\r\nsecure-library-loader decrypt my-lib.sll -o ./restored-lib --force\r\n```\r\n\r\n### Install into a `lib/` subfolder\r\n\r\n```bash\r\nsecure-library-loader install my-lib.sll --module-root . --lib-dir lib --name my-lib\r\n```\r\n\r\nThis decrypts into `<module-root>/lib/<name>` and prints the modules it found\r\ninside that subfolder. All flags are optional:\r\n- `--module-root` defaults to secure-library-loader's own package directory\r\n- `--lib-dir` defaults to `lib`\r\n- `--name` defaults to `my-lib.sll`'s basename (`my-lib`)\r\n\r\n### Using an environment variable instead of a prompt\r\n\r\n```bash\r\nexport SLL_PASSWORD=\"correct-horse-battery-staple\"\r\nsecure-library-loader encrypt ./my-lib -o my-lib.sll\r\nsecure-library-loader decrypt my-lib.sll -o ./restored-lib\r\n```\r\n\r\n## Programmatic API\r\n\r\n```js\r\nconst { encryptLibrarySync, decryptLibrarySync } = require(\"secure-library-loader\");\r\n\r\nencryptLibrarySync({\r\n  sourceDir: \"./my-lib\",\r\n  outputFile: \"./my-lib.sll\",\r\n  password: \"correct-horse-battery-staple\", // or set SLL_PASSWORD\r\n});\r\n\r\ndecryptLibrarySync({\r\n  inputFile: \"./my-lib.sll\",\r\n  outDir: \"./restored-lib\",\r\n  password: \"correct-horse-battery-staple\",\r\n  force: false, // set true to overwrite a non-empty outDir\r\n});\r\n```\r\n\r\n`encryptLibrary` / `decryptLibrary` are the same operations, but async so\r\nthey can prompt for a password on a TTY when none is provided.\r\n\r\n### `encryptLibrary(options)`\r\n\r\n| Option        | Type   | Required | Description                                                        |\r\n|---------------|--------|----------|----------------------------------------------------------------------|\r\n| `sourceDir`   | string | yes      | Directory whose files will be packed and encrypted                 |\r\n| `outputFile`  | string | yes      | Path to write the encrypted `.sll` file                            |\r\n| `password`    | string | no       | Falls back to `SLL_PASSWORD` env var, then an interactive prompt    |\r\n| `scryptCost`  | object | no       | Override scrypt cost `{ N, r, p }` (defaults to `{16384, 8, 1}`)    |\r\n\r\nReturns `{ outputFile, bytesWritten }`.\r\n\r\n### `encryptLibrarySync(options)`\r\n\r\nSynchronous counterpart of `encryptLibrary()`. Same options and return\r\nvalue. Interactive password prompt is not available — pass `password` or\r\nset `SLL_PASSWORD`.\r\n\r\n### `decryptLibrary(options)`\r\n\r\n| Option      | Type    | Required | Description                                                      |\r\n|-------------|---------|----------|--------------------------------------------------------------------|\r\n| `inputFile` | string  | yes      | Path to the encrypted `.sll` file                                 |\r\n| `outDir`    | string  | yes      | Directory to restore files into                                  |\r\n| `password`  | string  | no       | Falls back to `SLL_PASSWORD` env var, then an interactive prompt  |\r\n| `force`     | boolean | no       | Overwrite `outDir` even if it already exists and isn't empty      |\r\n\r\nReturns `{ outDir }`.\r\n\r\nThrows if the password is wrong, the file is corrupted/tampered with, or\r\n`outDir` already contains files and `force` isn't set. On any failure, no\r\npartial output is left behind — extraction happens in a temp directory and\r\nis only moved into place after it fully succeeds.\r\n\r\n### `decryptLibrarySync(options)`\r\n\r\nSynchronous counterpart of `decryptLibrary()`. Same options and return\r\nvalue. Interactive password prompt is not available — pass `password` or\r\nset `SLL_PASSWORD`.\r\n\r\n## Installing and loading modules at runtime\r\n\r\nEach encrypted library is installed into its **own named subfolder** inside\r\n`lib/`, so installing multiple libraries never overwrites each other:\r\n\r\n```\r\nlib/\r\n├── my-lib/       <- from my-lib.sll\r\n│   ├── greeter.js\r\n│   └── sub/adder.js\r\n└── other-lib/    <- from other-lib.sll\r\n    └── index.js\r\n```\r\n\r\nThe subfolder name comes from the `name` option, and **defaults to the\r\n`.sll` file's basename** (extension stripped) when omitted — e.g.\r\n`my-lib.sll` installs into `lib/my-lib` unless you pass a different `name`.\r\n\r\nBy default, `moduleRoot` is **secure-library-loader's own package\r\ndirectory** (exported as `PACKAGE_ROOT`) — not `process.cwd()` and not the\r\nconsuming app's directory. That means with no `moduleRoot` override,\r\ndecrypted files land in `<node_modules>/secure-library-loader/lib/<name>`,\r\nregardless of where your process is run from. Pass an explicit `moduleRoot`\r\n(e.g. `__dirname` of your own module) if you want the lib folder to live\r\nsomewhere else instead.\r\n\r\n```js\r\nconst { installLibrarySync, getModuleByName } = require(\"secure-library-loader\");\r\n\r\n// 1. Decrypt ./my-lib.sll into <moduleRoot>/lib/my-lib\r\n//    (name defaults to \"my-lib\" from the file's basename)\r\ninstallLibrarySync({\r\n  inputFile: \"./my-lib.sll\",\r\n  password: \"correct-horse-battery-staple\",\r\n  moduleRoot: __dirname, // defaults to secure-library-loader's own package root\r\n  force: false,\r\n});\r\n\r\n// 2. Load a module from inside that installed library and use it.\r\n//    Paths are \"<name>/<module>\", since each library lives in its own subfolder.\r\nconst greeter = getModuleByName(\"my-lib/greeter\", { moduleRoot: __dirname });\r\nconsole.log(greeter.greet(\"world\"));\r\n\r\n// Nested paths work too\r\nconst adder = getModuleByName(\"my-lib/sub/adder\", { moduleRoot: __dirname });\r\nconsole.log(adder(2, 3)); // 5\r\n```\r\n\r\nOr do both in one call:\r\n\r\n```js\r\nconst { loadEncryptedModuleSync } = require(\"secure-library-loader\");\r\n\r\nconst greeter = loadEncryptedModuleSync({\r\n  inputFile: \"./my-lib.sll\",\r\n  modulePath: \"greeter\", // file to load, relative to the installed library\r\n  name: \"my-lib\",        // optional; defaults to the .sll file's basename\r\n  password: process.env.SLL_PASSWORD,\r\n  moduleRoot: __dirname,\r\n});\r\n```\r\n\r\n### `installLibrary(options)`\r\n\r\n| Option       | Type    | Required | Description                                                       |\r\n|--------------|---------|----------|---------------------------------------------------------------------|\r\n| `inputFile`  | string  | yes      | Path to the encrypted `.sll` file                                  |\r\n| `password`   | string  | no       | Falls back to `SLL_PASSWORD` env var, then an interactive prompt   |\r\n| `moduleRoot` | string  | no       | Module root the lib folder sits in (defaults to secure-library-loader's own package root)   |\r\n| `libDir`     | string  | no       | Lib folder name, or an absolute path (defaults to `\"lib\"`)         |\r\n| `name`       | string  | no       | Subfolder name to install into, inside the lib folder (defaults to `inputFile`'s basename, extension stripped) |\r\n| `force`      | boolean | no       | Overwrite that subfolder if it exists and isn't empty              |\r\n\r\nReturns `{ libDir, name, installDir, modules }`:\r\n- `libDir` — the resolved base lib directory (e.g. `<moduleRoot>/lib`)\r\n- `name` — the library name actually used (useful when it was defaulted)\r\n- `installDir` — the specific subfolder decrypted into (`<libDir>/<name>`)\r\n- `modules` — the loadable module names inside `installDir`\r\n\r\n### `installLibrarySync(options)`\r\n\r\nSynchronous counterpart of `installLibrary()`. Same options and return\r\nvalue, but it blocks while deriving the key and writing files. Interactive\r\npassword prompt is not available — pass `password` or set `SLL_PASSWORD`.\r\n\r\n```js\r\nconst { installLibrarySync, getModuleByName } = require(\"secure-library-loader\");\r\n\r\ninstallLibrarySync({\r\n  inputFile: \"./my-lib.sll\",\r\n  password: \"correct-horse-battery-staple\",\r\n  moduleRoot: __dirname,\r\n});\r\n\r\nconst greeter = getModuleByName(\"my-lib/greeter\", { moduleRoot: __dirname });\r\n```\r\n\r\n### `getModuleByName(modulePath, options)`\r\n\r\nLoads a module from the lib folder and returns its exports.\r\n\r\n| Option       | Type    | Required | Description                                                       |\r\n|--------------|---------|----------|---------------------------------------------------------------------|\r\n| `moduleRoot` | string  | no       | Module root the lib folder sits in (defaults to secure-library-loader's own package root)   |\r\n| `libDir`     | string  | no       | Lib folder name, or an absolute path (defaults to `\"lib\"`)         |\r\n| `reload`     | boolean | no       | Bypass the require cache and re-execute the module                |\r\n\r\n`modulePath` is resolved relative to the lib folder using normal Node\r\nresolution, so `\"my-lib/greeter\"` finds `my-lib/greeter.js`, and accepts\r\nfurther nesting like `\"my-lib/sub/adder\"`. It rejects absolute paths or\r\nanything containing `..`. Since `installLibrary()` puts each library in its\r\nown `<name>/` subfolder, `modulePath` will typically start with that name.\r\n\r\n### `loadEncryptedModule(options)`\r\n\r\nConvenience wrapper: `installLibrary()` followed by `getModuleByName()`.\r\nTakes all of `installLibrary`'s options (including the optional `name` for\r\nthe install subfolder) plus a required `modulePath` — the file to load,\r\nrelative to that installed subfolder (e.g. `\"greeter\"`, not `\"my-lib/greeter\"`).\r\n\r\n`loadEncryptedModuleSync(options)` is the synchronous counterpart.\r\nInteractive password prompt is not available — pass `password` or set\r\n`SLL_PASSWORD`.\r\n\r\n### `listModules(options)`\r\n\r\n```js\r\nasync function listModules({ moduleRoot, libDir, name } = {}): Promise<string[]>\r\n```\r\n\r\nWithout `name`, lists the **installed library names** at the top of the lib\r\nfolder (i.e. the `name` each was installed under). With `name`, lists the\r\nmodules inside that specific installed library — i.e. what you can pass to\r\n`getModuleByName()` as `\"<name>/<module>\"`.\r\n\r\n| Option       | Type   | Required | Description                                                     |\r\n|--------------|--------|----------|--------------------------------------------------------------------|\r\n| `moduleRoot` | string | no       | Module root the lib folder sits in (defaults to secure-library-loader's own package root) |\r\n| `libDir`     | string | no       | Lib folder name, or an absolute path (defaults to `\"lib\"`)       |\r\n| `name`       | string | no       | Scope the listing to this installed library's subfolder instead of the lib folder root |\r\n\r\nReturns a sorted array of names: each directory, plus each `.js` / `.cjs` /\r\n`.mjs` / `.json` / `.node` file with its extension stripped. If the target\r\ndirectory doesn't exist yet, it resolves to `[]` rather than throwing.\r\n\r\n```js\r\nconst { installLibrarySync, listModulesSync } = require(\"secure-library-loader\");\r\n\r\ninstallLibrarySync({ inputFile: \"./my-lib.sll\", password: \"pw\", moduleRoot: __dirname });\r\n\r\nconsole.log(listModulesSync({ moduleRoot: __dirname }));\r\n// => [\"my-lib\"]\r\n\r\nconsole.log(listModulesSync({ moduleRoot: __dirname, name: \"my-lib\" }));\r\n// => [\"adder\", \"config\", \"greeter\"]\r\n```\r\n\r\n`installLibrary()` also returns the second list directly as `result.modules`,\r\nso you don't need a separate call right after installing.\r\n\r\n`listModulesSync(options)` is the synchronous counterpart and returns the\r\nsame array immediately.\r\n\r\n### `unloadModule(name, options)`\r\n\r\n```js\r\nfunction unloadModule(name, { moduleRoot, libDir } = {}): boolean\r\n```\r\n\r\nEvicts a previously loaded module from Node's `require` cache, so the next\r\n`getModuleByName()` call for that name re-reads and re-executes the file\r\nfrom disk instead of returning the cached exports.\r\n\r\n| Option       | Type   | Required | Description                                                     |\r\n|--------------|--------|----------|--------------------------------------------------------------------|\r\n| `moduleRoot` | string | no       | Module root the lib folder sits in (defaults to secure-library-loader's own package root) |\r\n| `libDir`     | string | no       | Lib folder name, or an absolute path (defaults to `\"lib\"`)       |\r\n\r\nReturns `true` if a cached entry was found and removed, `false` if the\r\nmodule was never loaded (or doesn't resolve) — it never throws for that\r\ncase, so it's safe to call speculatively.\r\n\r\n```js\r\nconst { getModuleByName, unloadModule } = require(\"secure-library-loader\");\r\n\r\nconst greeter = getModuleByName(\"my-lib/greeter\", { moduleRoot: __dirname });\r\n\r\n// ... later, e.g. after re-installing a newer encrypted library on disk ...\r\nunloadModule(\"my-lib/greeter\", { moduleRoot: __dirname }); // => true\r\n\r\nconst fresh = getModuleByName(\"my-lib/greeter\", { moduleRoot: __dirname });\r\nfresh !== greeter; // true — re-executed from the file on disk\r\n```\r\n\r\nNote this only clears Node's module cache; it does not delete the decrypted\r\nfiles from the lib folder. To remove the files too, re-run `installLibrary()`\r\nwith `{ force: true }`, or delete the lib directory yourself.\r\n\r\n### A note on executing decrypted code\r\n\r\n`getModuleByName()` calls `require()` under the hood, which **executes** the\r\ndecrypted module's code in your process. That is the point of the function,\r\nbut it means the trust boundary is: *anyone who can supply both an encrypted\r\nfile and its password can run arbitrary code in your process.*\r\n\r\nThis is safe when you encrypted the library yourself — the AES-GCM\r\nauthentication tag guarantees nobody modified it in transit. Do not point it\r\nat encrypted bundles from untrusted sources.\r\n\r\nNote also that this library deliberately has **no `postinstall` hook**.\r\nDecryption only ever happens when you explicitly call it. Packages that\r\ndecrypt and execute code automatically at install time are a well-known\r\nmalware pattern and get flagged by registry scanners.\r\n\r\n## How it works\r\n\r\n**Encrypted container format** (produced by `encryptLibrary`):\r\n\r\n```\r\nmagic        \"SLL1\"      4 bytes    format identifier\r\nversion      uint8       1 byte\r\nkdf id       uint8       1 byte     1 = scrypt\r\nN, r, p      uint32 x3   12 bytes   scrypt cost parameters\r\nsalt len     uint8       1 byte\r\nsalt         N bytes     random, unique per encryption\r\niv           12 bytes    random, unique per encryption\r\nauthTag      16 bytes    AES-GCM authentication tag\r\nciphertext   remainder   gzip-compressed archive, encrypted\r\n```\r\n\r\nStoring the KDF cost parameters in the header means future versions can\r\nraise the cost factor without breaking files encrypted with older defaults.\r\n\r\n**Archive format** (the plaintext packed before encryption): a flat list of\r\n`{ path, mode, content }` entries covering every regular file under\r\n`sourceDir`. Directory structure is preserved via `/`-separated relative\r\npaths; empty directories and symlinks are not preserved.\r\n\r\n**Security properties**:\r\n\r\n- AES-256-GCM is authenticated — any tampering with the encrypted file is\r\n  detected and decryption fails, rather than silently returning corrupted\r\n  data.\r\n- A fresh random salt and IV are generated on every encryption, so\r\n  encrypting the same content twice with the same password produces\r\n  different ciphertext.\r\n- Every extracted file path is validated to resolve inside the target\r\n  directory, rejecting archive entries that attempt path traversal\r\n  (e.g. `../../.npmrc`).\r\n- Decryption is atomic: files are extracted to a temporary directory first,\r\n  then moved into place, so a crash mid-extraction never leaves a\r\n  half-written library folder.\r\n\r\n## Development\r\n\r\n```bash\r\nnpm test\r\n```\r\n\r\nRuns the test suite with Node's built-in test runner (`node --test`),\r\ncovering encryption round-trips, wrong-password/tamper detection,\r\npath-traversal protection, and module install/load behaviour.\r\n","readmeFilename":"README.md"}