{"record":{"id":"d56257c26219bb0d","repo":"mozilla/pdf.js","slug":"doc-info-prop-is-read-only","errorCode":null,"errorMessage":"doc.info.${prop} is read-only","messagePattern":"doc\\.info\\.(.+?) is read-only","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/scripting_api/doc.js","lineNumber":30,"sourceCode":" * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { makeArr, makeMap, serializeError } from \"./app_utils.js\";\nimport { createMap } from \"./common.js\";\nimport { PDFObject } from \"./pdf_object.js\";\nimport { PrintParams } from \"./print_params.js\";\nimport { ZoomType } from \"./constants.js\";\n\nconst DOC_EXTERNAL = false;\n\nclass InfoProxyHandler {\n  static get(obj, prop) {\n    return obj[prop.toLowerCase()];\n  }\n\n  static set(obj, prop, value) {\n    throw new Error(`doc.info.${prop} is read-only`);\n  }\n}\n\nclass Doc extends PDFObject {\n  #pageActions = null;\n\n  #otherPageActions = null;\n\n  constructor(data) {\n    super(data);\n\n    // In a script doc === this.\n    // So adding a property to the doc means adding it to this\n    this._expandos = globalThis;\n\n    this._baseURL = data.baseURL || \"\";\n    this._calculate = true;\n    this._delay = false;","sourceCodeStart":12,"sourceCodeEnd":48,"githubUrl":"https://github.com/mozilla/pdf.js/blob/5903d58d58e4dd9ce6ffa3834aea8480f06b4ada/src/scripting_api/doc.js#L12-L48","documentation":"`doc.info` is a `Proxy` (handler `InfoProxyHandler` in doc.js) over the document's Info dictionary: title, author, authors, subject, keywords, creator, producer, creationdate, moddate, trapped. Reads are case-insensitive; the `set` trap throws `Error(\"doc.info.${prop} is read-only\")` for ANY written property name, interpolating the key. So even writing an unknown key throws.","triggerScenarios":"Any sandboxed assignment to an info sub-property: `doc.info.Title = \"x\"`, `doc.info[\"author\"] = \"y\"`, or `doc.info.custom = 1` (custom also throws because the trap is unconditional).","commonSituations":"Scripts ported from old Acrobat versions where info was mutable; forms that try to write the logged-in user into `doc.info.author` on save; metadata-stamping scripts.","solutions":["Do not assign to `doc.info.*`; treat it as read-only metadata.","Set title/author/subject/keywords in the PDF's Info dictionary at authoring time and re-export.","Wrap the assignment in try/catch if you must run untrusted/portable PDFs unchanged."],"exampleFix":"// before\ndoc.info.title = \"Annual Report\";\n// after\n// info is read-only in pdf.js; set the title in the PDF's Info dictionary at creation, then read doc.info.title","handlingStrategy":"try-catch","validationCode":"// doc.info is a Proxy whose set trap always throws; there is no // writable sub-property, so do not attempt any assignment to it.\n// Read-only access is safe: const title = doc.info.title;","typeGuard":"// Every key on doc.info is read-only -> the only safe operation is read.\nconst isInfoWriteSafe = () => false;","tryCatchPattern":"try {\n  doc.info[prop] = value;\n} catch (e) {\n  if (/doc\\.info\\..*is read-only/.test(e.message)) { /* expected; ignore */ }\n  else { throw e; }\n}","preventionTips":["Never assign to `doc.info` or any of its sub-properties in pdf.js scripts.","Edit metadata at the PDF source (Info dictionary), not from embedded JavaScript.","If processing third-party PDFs, keep a try/catch boundary around sandboxed script execution."],"tags":["scripting","acrobat-api","read-only","proxy","metadata"],"backgroundTag":null,"analyzedSha":"5903d58d58e4dd9ce6ffa3834aea8480f06b4ada","analyzedAt":"2026-08-13T02:28:27.364Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}