Valid .deepnote files are rejected when a block's content merely looks like a YAML anchor, alias or tag · Issue #571 · deepnote/deepnote · GitHub
Skip to content

Valid .deepnote files are rejected when a block's content merely looks like a YAML anchor, alias or tag #571

Open

Description

@jamesbhobbs

Verified against main (aa891df).

Not what I first filed

I originally filed this as "YAML aliases are rejected, so support them on read". That was wrong twice over, and I want the wrong version on the record rather than quietly replaced:

  • The prohibition is deliberate and correct. validateYamlStructure in packages/blocks/src/deepnote-file/parse-yaml.ts rejects anchors, aliases, merge keys and custom tags with a dedicated ProhibitedYamlFeatureError, and parse-yaml.test.ts has a describe('anchor and alias rejection') suite covering each. Anchors and aliases are the billion-laughs expansion vector and tags are a deserialization vector; a parser reading synced files from a workspace should refuse all four.
  • So "support aliases on read" was actively bad advice and is withdrawn.
  • The other half of what I suggested — "reject them with a message that names the construct" — is already implemented. The message names the feature exactly.

The actual bug

validateYamlStructure runs as a regex over the raw text, before the document is parsed. It therefore cannot tell a YAML anchor from text that merely looks like one inside a block's content: scalar — and block content is arbitrary user text.

A file with no YAML anchor at all is rejected:

        - type: code
          content: |-
            # C-style pointer notes
            ptr: &value
            deref: *value
Parse error: YAML anchors (&) are not allowed in Deepnote files

The yaml library disagrees, and it is right:

doc.errors.length            → 0
doc.anchors.getNames()       → []
typeof blocks[0].content     → "string"

There is no anchor. The content is one literal string inside a block scalar.

Why this is reachable, not theoretical

The most likely way a real notebook contains &anchor is a markdown cell documenting a YAML config — CI, docker-compose, Kubernetes — which is exactly where anchors are idiomatic:

        - type: markdown
          content: |-
            ## Our CI config

            ```yaml
            defaults: &defaults
              image: python:3.11
            test:
              <<: *defaults
            ```

That notebook cannot be read. A notebook about YAML is the one thing this format cannot store.

The tag rule has the same shape and a wider blast radius, because key: !word is common in CSS, shell and config snippets:

        - type: code
          content: |-
            flag: !important
Parse error: YAML tags are not allowed in Deepnote files: !important

Suggested direction

Validate the parsed document, not the raw text. parse-yaml.ts already calls parseDocument, and walking the resulting AST with visit distinguishes a real construct from text that resembles one. Verified against main:

Input anchors aliases mergeKeys tags
a: &x [1,2] / b: *x ["x"] 1 0 —
the same text inside a content: block scalar [] 0 0 —
base: &b {x: 1} / <<: *b ["b"] 1 1 —
a: !mytag 5 — — — ["!mytag"]
flag: !important inside a content: block scalar — — — []

The predicates that produce that, all from the yaml package:

  • anchor — node.anchor is set
  • alias — isAlias(node)
  • merge key — isPair(node) && isScalar(node.key) && node.key.value === '<<'
  • tag — node.tag is set

Each one keeps the prohibition exactly as strict against the real construct while being blind to block content, which is where every false positive comes from.

Two caveats for whoever picks this up:

  • Do not relax the prohibition while fixing the false positives. The point is to reject real anchors more precisely, not fewer of them.
  • Parsing first means untrusted input reaches parseDocument before validation, so the expansion-bomb concern has to be handled separately. maxAliasCount is the lever, but note it applies at toJS(), not at parseDocument — doc.toJS({ maxAliasCount: 0 }) throws ReferenceError: Alias resolution is disabled, whereas passing it to parseDocument does nothing. (I checked this because my first draft of this issue asserted the opposite.) Building the document is bounded by input size; only resolution expands, so validating the AST before any toJS() call is safe, and maxAliasCount: 0 is the belt to the AST check's braces.

A line number in the message would also help, but the false positives are the part that loses data.

Why it matters beyond parsing

A rejected file is a project missing from anything that reads the synced tree. deepnote lint fails outright on it. The workspace-wide checks in #555–#560 report the count of unparseable files, so it is not fully silent there — but a false rejection means the corpus is smaller than it should be for a reason nobody would think to look for, and "we could not read 3 files" reads like a data problem rather than a bug in the reader.

Activity

  1. changed the title [-].deepnote files using YAML aliases fail to parse, silently shrinking every workspace-wide check[/-] [+]Valid .deepnote files are rejected when a block's content merely looks like a YAML anchor, alias or tag[/+] on Oct 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions