Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,6 @@ doc
benchmark/implementations/*
!/benchmark/implementations/current/
dist/
.claude/
.codex/
.agents/
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [4.0.0] - 2021-01-03
### Changed
- Check [migration guide](migrate_v3_to_v4.md) to see details for all breaking changes.
- Check migration guide in [docs](docs/) for details of all breaking changes.
- Breaking: "unsafe" tags `!!js/function`, `!!js/regexp`, `!!js/undefined` are
moved to [js-yaml-js-types](https://github.com/nodeca/js-yaml-js-types) package.
- Breaking: removed `safe*` functions. Use `load`, `loadAll`, `dump`
Expand Down
16 changes: 8 additions & 8 deletions bin/js-yaml.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ const yaml = require('..')
/// /////////////////////////////////////////////////////////////////////////////

const cli = new argparse.ArgumentParser({
prog: 'js-yaml',
add_help: true
prog: 'js-yaml',
add_help: true
})

cli.add_argument('-v', '--version', {
Expand All @@ -19,26 +19,26 @@ cli.add_argument('-v', '--version', {
})

cli.add_argument('-c', '--compact', {
help: 'Display errors in compact mode',
help: 'Display errors in compact mode',
action: 'store_true'
})

// deprecated (not needed after we removed output colors)
// option suppressed, but not completely removed for compatibility
cli.add_argument('-j', '--to-json', {
help: argparse.SUPPRESS,
dest: 'json',
help: argparse.SUPPRESS,
dest: 'json',
action: 'store_true'
})

cli.add_argument('-t', '--trace', {
help: 'Show stack trace on error',
help: 'Show stack trace on error',
action: 'store_true'
})

cli.add_argument('file', {
help: 'File to read, utf-8 encoded without BOM',
nargs: '?',
help: 'File to read, utf-8 encoded without BOM',
nargs: '?',
default: '-'
})

Expand Down
File renamed without changes.
57 changes: 57 additions & 0 deletions docs/safety.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Safety notes for untrusted input

The YAML spec by design allows compact documents that produce objects which are
expensive to materialize. Guard against this when the input is untrusted.

### 1. Limit input size

Limit the input size to the smallest acceptable value.

### 2. Catch all exceptions

`load()` throws on malformed input. Always wrap it in `try/catch` and treat any
error as a rejected document.

### 3. Traverse with a node limit before materializing

Aliases let a tiny document expand into a huge object graph (the "billion
laughs" pattern) and can even create circular references. It stays cheap until
you fully walk it (`JSON.stringify`, deep clone, recursive validation), so do a
cheap pass first that counts every element and bails out past a limit.

The walk is a flat loop over a manual stack — no recursion, so deep documents
can't overflow the call stack. Counting happens as each element is discovered,
so the stack never grows past the limit:

```js
// plain `{}` or `Object.create(null)`, but not Date / Uint8Array / etc.
function isContainer(o) {
if (Array.isArray(o)) return true
if (!o || typeof o !== 'object') return false
const proto = Object.getPrototypeOf(o)
return proto === Object.prototype || proto === null
}

function guardNodeCount(root, limit) {
let count = 0
const stack = [ root ]

while (stack.length) {
const node = stack.pop()

for (const key in node) {
if (++count > limit) throw new Error('Too many nodes')
const value = node[key]
if (isContainer(value)) stack.push(value)
}
}
}

const data = yaml.load(input)
guardNodeCount(data, 100000)
const json = JSON.stringify(data)
```

Note: aliases that point to the same node are counted every time they appear
(matching the real materialization cost), and a cyclic document keeps producing
elements until it hits the limit — both are rejected as intended.
3 changes: 0 additions & 3 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,6 @@ export default [

{
rules: {
camelcase: 'off',
'@stylistic/key-spacing': 'off',
'no-useless-escape': 'off',
'object-shorthand': 'off',
}
}
Expand Down
4 changes: 2 additions & 2 deletions examples/dumper.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ const object = require('./dumper.json')
console.log(yaml.dump(object, {
flowLevel: 3,
styles: {
'!!int' : 'hexadecimal',
'!!null' : 'camelcase'
'!!int': 'hexadecimal',
'!!null': 'camelcase'
}
}))

Expand Down
24 changes: 12 additions & 12 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,19 @@ module.exports.YAMLException = require('./lib/exception')

// Re-export all types in case user wants to create custom schema
module.exports.types = {
binary: require('./lib/type/binary'),
float: require('./lib/type/float'),
map: require('./lib/type/map'),
null: require('./lib/type/null'),
pairs: require('./lib/type/pairs'),
set: require('./lib/type/set'),
binary: require('./lib/type/binary'),
float: require('./lib/type/float'),
map: require('./lib/type/map'),
null: require('./lib/type/null'),
pairs: require('./lib/type/pairs'),
set: require('./lib/type/set'),
timestamp: require('./lib/type/timestamp'),
bool: require('./lib/type/bool'),
int: require('./lib/type/int'),
merge: require('./lib/type/merge'),
omap: require('./lib/type/omap'),
seq: require('./lib/type/seq'),
str: require('./lib/type/str')
bool: require('./lib/type/bool'),
int: require('./lib/type/int'),
merge: require('./lib/type/merge'),
omap: require('./lib/type/omap'),
seq: require('./lib/type/seq'),
str: require('./lib/type/str')
}

// Removed functions from JS-YAML 3.0.x
Expand Down
Loading