GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — diff - `--palette` values are used verbatim; backslash escapes are not decoded · Issue #362 · uutils/diffutils

diff - --palette values are used verbatim; backslash escapes are not decoded #362

New issue
Copy link
New issue
Copy link
Open
#383
Open
diff - --palette values are used verbatim; backslash escapes are not decoded#362
#383
Copy link
Labels
bugSomething isn't workingSomething isn't workinggnu-compatBehaviour differs from GNUBehaviour differs from GNUgood first issueGood for newcomersGood for newcomersoss-scannerFound by Anthropic's OSS ScannerFound by Anthropic's OSS Scanner

Description

@sylvestre
sylvestre
opened on Nov 9, 2026
Issue body actions

GNU diff --palette parses each value like dircolors/LS_COLORS: backslash escapes
(\e, \a, \\, \:, octal \NNN, hex \xHH, ...) and caret notation (^[) are
decoded before the value is wrapped in ESC[ ... m. uutils copies the value byte for
byte, so any escaped value ends up as literal backslashes in the output.

Reproduction

printf 'a\nb\n' > old; printf 'a\nc\n' > new

Rust (incorrect)

$ ./target/release/diffutils diff --color=always --palette='ad=\061;32' old new | tail -1 | cat -v
^[[\061;32m> c^[[0m
# Exit code: 0   (of the pipeline; diff itself exits 1)

GNU (correct)

$ LC_ALL=C diff --color=always --palette='ad=\061;32' old new | tail -1 | cat -v
^[[1;32m> c^[[0m
# Exit code: 0   (of the pipeline; diff itself exits 1)

(\061 is octal for 1.)

Root cause

Palette::parse in src/color.rs:55-72 splits on : and = and stores the raw value:

for entry in spec.split(':').filter(|entry| !entry.is_empty()) {
    let Some((name, value)) = entry.split_once('=') else {
        return Err(PaletteError::Unparsable);
    };
    ...
    value.clone_into(slot);

Splitting on : first also means an escaped \: cannot appear in a value.

Test

Add to tests/by-util/test_diff.rs:

#[test]
fn palette_decodes_escapes() -> Result<(), Box<dyn std::error::Error>> {
    let mut old = NamedTempFile::new()?;
    old.write_all(b"a\nb\n")?;
    let mut new = NamedTempFile::new()?;
    new.write_all(b"a\nc\n")?;
    let mut cmd = cargo_bin_cmd!("diffutils");
    cmd.arg("diff")
        .arg("--color=always")
        .arg(r"--palette=ad=\061;32")
        .arg(old.path())
        .arg(new.path());
    cmd.assert()
        .code(predicate::eq(1))
        .stdout(predicate::str::contains("\x1b[1;32m> c\x1b[0m"));
    Ok(())
}

Reported by Anthropic's OSS Scanner (ANT-2026-JH4TKA3A).

Activity

  1. sylvestre
    added
    bugSomething isn't workingSomething isn't working
    good first issueGood for newcomersGood for newcomers
    oss-scannerFound by Anthropic's OSS ScannerFound by Anthropic's OSS Scanner
    gnu-compatBehaviour differs from GNUBehaviour differs from GNU
    on Nov 9, 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

    bugSomething isn't workingSomething isn't workinggnu-compatBehaviour differs from GNUBehaviour differs from GNUgood first issueGood for newcomersGood for newcomersoss-scannerFound by Anthropic's OSS ScannerFound by Anthropic's OSS Scanner

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      You are visitor 01402503 — GeezerVison does not store the pages you look up.
      Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.