Repository navigation
Harden dependencies and remote fetches - #109
Conversation
Reject HTTPS snippets that redirect to HTTP unless allowInsecureHttp is set, and stream remote bodies with a 5 MB limit. Replace fast-glob with tinyglobby, removing the only production advisories (micromatch/braces); sort results for a stable order. Drop the deprecated mdast package, move @types/mdast to devDependencies, and stop publishing the 1.3 MB logo.
tinyglobby 0.2.17 drops the working directory's own subtree from a ../ pattern and, in some layouts, symlinked directories under a symlinked absolute root, so md-code check --markdown-glob '../**/*.md' found nothing. Go back to fast-glob, keep sorted results, drop the empty-pattern guard that was only needed for tinyglobby, and add glob tests for both cases. Regenerate the lockfile from main so it only drops mdast.
Checking only the final URL let an HTTP hop in the middle of a redirect chain through (https -> http -> https), and an insecure hop was requested before being rejected. Follow up to 10 redirects with redirect: 'manual', resolving relative Locations and rejecting http: (unless allowInsecureHttp) and non-HTTP schemes before requesting them.
Second-pass reviewFour passes: dependencies, then remote fetch three times. Dependencies. All 9 fixtures run through
Fixed: I kept fast-glob and added a Remote fetch, first pass: real Fixed: redirects are followed manually ( Remote fetch, recheck: the
Other checks:
Fixed in 328c09f: redirects to URLs with credentials are rejected without echoing them, an invalid Not changed: if a body is shorter than its CI green on Ubuntu, macOS and Windows × Node 22 and 24, including |
Summary
Remote-fetch and package hardening. A redirect can no longer downgrade a remote snippet to plain HTTP, remote bodies are size-limited, a deprecated package is gone, and the published tarball shrinks from 1.3 MB to 31 KB.
Remote snippets
Redirects bypassed
allowInsecureHttp: false. Node'sfetchfollows HTTPS → HTTP redirects silently. Checked against a live endpoint:fetchRemoteContentnow follows redirects itself (redirect: 'manual', at most 10). It resolves relativeLocations and validates every hop before requesting it:http:is rejected unlessallowInsecureHttpis on. That includes anhttp:hop in the middle of a chain (https → http → https), where a network attacker could otherwise point the last hop anywhere:file:,data:) are rejected, as are URLs with credentials (without echoing them) and unparseableLocations.Too many redirects.No size limit. The response used to be read with
response.text(), however large. Bodies are now streamed and rejected past 5 MB: up front fromContent-Length, or as soon as the streamed body passes the limit. The limit counts decompressed bytes, so a gzip bomb is caught. Decoding matchesresponse.text()exactly: BOM stripped, invalid UTF-8 becomes U+FFFD, multi-byte characters split across chunks decode correctly. The timeout still covers the whole redirect chain and the body. Error responses have their body cancelled, so a stalled 500 can't keep the process alive. The limit is aFetchOptions.maxBytesparameter, not a config option. The README documents both rules.Dependencies and package
mdast@3.0.0, a deprecated stub (npm: "mdastwas renamed toremark"); the code only uses its types.@types/mdastmoved to devDependencies, becausedist/index.d.tshas no imports. A consumer project compiled withskipLibCheck: falseand no@types/mdast.markdown-code.pngis no longer published. The README uses the GitHub raw URL (checked:200 image/png), and the smoke test's allowed-files list is updated.micromatch/bracesadvisories (stack exhaustion on deeply nested brace patterns, no fixed release). The review found tinyglobby 0.2.17 drops the working directory's own files from a../pattern:md-code check --markdown-glob '../**/*.md'run fromdocs/found nothing. Glob patterns come only from the user's own config or CLI, so the advisory isn't reachable by an outside attacker.glob.test.tsnow covers the../case so a future swap gets caught. The lockfile diff only removesmdast.Tests
remote.test.ts: stubs now return realResponses, since the body is streamed.Location; non-HTTP scheme; credentials; invalidLocation; 10-redirect limit; 3xx withoutLocation.Content-Lengthover the limit, streamed body over the limit, exactly at the limit, and UTF-8 split across chunks.glob.test.ts:../includes the working directory's own files; symlinked directories under a symlinked absolute root are followed.End to end: a local redirect chain (
/start → /middle → final?x=1) works, and the live HTTPS→HTTP redirect is rejected.I have run
npm run lintandnpm run format:check.I have run
npm run buildto verify the project builds.I have run
npm testto ensure all tests pass. (340 passed, plusnpm run test:package)