{"record":{"id":"e50fd25aa0132adf","repo":"FiloSottile/age","slug":"trailing-data-after-armored-file","errorCode":null,"errorMessage":"trailing data after armored file","messagePattern":"trailing data after armored file","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"armor/armor.go","lineNumber":123,"sourceCode":"\t\tline, err := r.r.ReadBytes('\\n')\n\t\tif err == io.EOF && len(line) == 0 {\n\t\t\treturn nil, io.ErrUnexpectedEOF\n\t\t} else if err != nil && err != io.EOF {\n\t\t\treturn nil, err\n\t\t}\n\t\tline = bytes.TrimSuffix(line, []byte(\"\\n\"))\n\t\tline = bytes.TrimSuffix(line, []byte(\"\\r\"))\n\t\treturn line, nil\n\t}\n\n\tconst maxWhitespace = 1024\n\tdrainTrailing := func() error {\n\t\tbuf, err := io.ReadAll(io.LimitReader(r.r, maxWhitespace))\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif len(bytes.TrimSpace(buf)) != 0 {\n\t\t\treturn errors.New(\"trailing data after armored file\")\n\t\t}\n\t\tif len(buf) == maxWhitespace {\n\t\t\treturn errors.New(\"too much trailing whitespace\")\n\t\t}\n\t\treturn io.EOF\n\t}\n\n\tvar removedWhitespace int\n\tfor !r.started {\n\t\tline, err := getLine()\n\t\tif err != nil {\n\t\t\treturn 0, r.setErr(err)\n\t\t}\n\t\t// Ignore leading whitespace.\n\t\tif len(bytes.TrimSpace(line)) == 0 {\n\t\t\tremovedWhitespace += len(line) + 1\n\t\t\tif removedWhitespace > maxWhitespace {\n\t\t\t\treturn 0, r.setErr(errors.New(\"too much leading whitespace\"))","sourceCodeStart":105,"sourceCodeEnd":141,"githubUrl":"https://github.com/FiloSottile/age/blob/b74dce4cdbe35b5e5f66c06d9612b72f89028758/armor/armor.go#L105-L141","documentation":"When the armored reader encounters the footer, drainTrailing reads up to maxWhitespace extra bytes to confirm only whitespace follows the armored file. If any non-whitespace bytes are found after the footer, the reader reports this error, since valid ASCII armor must end right after the footer line.","triggerScenarios":"Reading (via armor.NewReader, e.g. through age.Decrypt on an armored file) a stream that contains additional non-whitespace content after the '--- END AGE ENCRYPTED FILE ---' line — concatenated armored files, appended notes/signatures, or binary junk after the armor.","commonSituations":"Concatenating two armored age files with cat and decrypting the result, scripts appending logs/signatures after the armor block, or downloads that include HTML/error text after the armored payload.","solutions":["Remove everything after the footer line from the armored file (extract only the BEGIN...END block).","If concatenation was intended, split the file and decrypt each armored section separately.","Re-transfer or re-export the file to get a clean armored document."],"exampleFix":"// before: file.age contains armor followed by extra text\ncat a.age b.age > c.age\n// after\ncat a.age > c.age\n# or decrypt a.age and b.age separately","handlingStrategy":"validation","validationCode":"// Validate an armored file ends right after the footer\nfunc hasTrailingData(data []byte) bool {\n    marker := []byte(\"--- END AGE ENCRYPTED FILE---\")\n    i := bytes.LastIndex(data, marker)\n    if i < 0 {\n        return false\n    }\n    return len(bytes.TrimSpace(data[i+len(marker):])) > 0\n}","typeGuard":null,"tryCatchPattern":"_, err := io.ReadAll(armor.NewReader(f))\nif err != nil && err.Error() == \"trailing data after armored file\" {\n    // extract only the BEGIN..END block and retry\n}","preventionTips":["Never concatenate armored files; keep one armor block per file.","Strip any text appended after the END footer before decrypting.","Download .age files in binary mode to avoid appended content."],"tags":["go","age","armor","reader","trailing-data"],"backgroundTag":"trailing-data-after-armor","analyzedSha":"b74dce4cdbe35b5e5f66c06d9612b72f89028758","analyzedAt":"2026-08-31T23:59:31.627Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}