{"record":{"id":"abd644d533dff05b","repo":"OtterMind/Chat2DB","slug":"unsafe-column-type-name-from-metadata-typename","errorCode":null,"errorMessage":"Unsafe column type name from metadata: {typeName}","messagePattern":"Unsafe column type name from metadata: (.+?)","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"chat2db-community-server/chat2db-community-plugins/chat2db-community-h2/src/main/java/ai/chat2db/plugin/h2/H2SqlGuards.java","lineNumber":50,"sourceCode":"        \"(?i)^(?:NOW|RANDOM_UUID|UUID)\\\\(\\\\s*(?:\\\\d+)?\\\\s*\\\\)$\");\n    private static final Pattern TYPED_LITERAL = Pattern.compile(\n        \"(?i)^(?:DATE|TIME(?:\\\\s+WITH\\\\s+TIME\\\\s+ZONE)?|TIMESTAMP(?:\\\\s+WITH\\\\s+TIME\\\\s+ZONE)?|UUID|JSON|GEOMETRY)\\\\s+\"\n            + STRING_LITERAL_SOURCE + \"$\");\n    private static final Pattern BINARY_LITERAL = Pattern.compile(\"(?i)^(?:X|BINARY)\\\\s*'[0-9A-F]*'$\");\n    private static final Pattern SEQUENCE_EXPRESSION = Pattern.compile(\n        \"(?i)^NEXT\\\\s+VALUE\\\\s+FOR\\\\s+\" + IDENTIFIER_SOURCE + \"(?:\\\\.\" + IDENTIFIER_SOURCE + \")?$\");\n\n    private H2SqlGuards() {\n    }\n\n    /**\n     * Validates a column type name obtained from JDBC metadata before it is embedded\n     * into generated DDL. Returns the type name unchanged when it matches the\n     * allow-list; throws otherwise (fail closed).\n     */\n    public static String requireSafeTypeName(String typeName) {\n        if (typeName != null && !SAFE_TYPE_NAME.matcher(typeName).matches()) {\n            throw new IllegalArgumentException(\"Unsafe column type name from metadata: \" + typeName);\n        }\n        return typeName;\n    }\n\n    /**\n     * Reconstructs a type declaration from JDBC metadata without treating display width as a\n     * type parameter. H2 reports values such as 64 for BIGINT and 26 for TIMESTAMP in\n     * COLUMN_SIZE, but those values are not legal declarations for these types.\n     */\n    public static String renderMetadataType(String typeName, int dataType, int columnSize, int decimalDigits) {\n        String safeTypeName = requireSafeTypeName(typeName);\n        StringBuilder declaration = new StringBuilder(safeTypeName);\n        switch (dataType) {\n            case Types.CHAR:\n            case Types.VARCHAR:\n            case Types.NCHAR:\n            case Types.NVARCHAR:\n            case Types.BINARY:","sourceCodeStart":32,"sourceCodeEnd":68,"githubUrl":"https://github.com/OtterMind/Chat2DB/blob/5ee1e990e73fbcae1969dc554be254fedb3ab888/chat2db-community-server/chat2db-community-plugins/chat2db-community-h2/src/main/java/ai/chat2db/plugin/h2/H2SqlGuards.java#L32-L68","documentation":"Thrown by H2SqlGuards.requireSafeTypeName when a non-null JDBC metadata column type name does not match the allow-list ^[A-Za-z][A-Za-z0-9_]*(\\s+[A-Za-z][A-Za-z0-9_]*)*$. The guard prevents hostile or corrupt metadata from injecting SQL into generated DDL; null is permitted and returned unchanged.","triggerScenarios":"Calling requireSafeTypeName(typeName) or renderMetadataType(...) where typeName contains punctuation, digits-leading, parentheses, commas, or operators (e.g. \"VARCHAR(255)\", \"INT,\", \"1TYPE\"). Triggered when rebuilding a table DDL from H2 JDBC metadata that returns an unexpected TYPE_NAME.","commonSituations":"An H2 version or custom domain type whose TYPE_NAME includes parameter syntax; a corrupted metadata row; a column whose type name was manually edited to include size arguments.","solutions":["Strip type parameters before calling requireSafeTypeName; renderMetadataType appends size/precision itself from columnSize/decimalDigits.","If the type name legitimately needs special characters, whitelist it explicitly or handle that column outside the metadata-driven path.","Upgrade/verify the H2 driver returns a clean TYPE_NAME for the column."],"exampleFix":"// before\nString t = \"VARCHAR(255)\"; // parentheses fail the allow-list\nH2SqlGuards.requireSafeTypeName(t);\n\n// after\nString t = \"VARCHAR\"; // base name only\nH2SqlGuards.renderMetadataType(t, Types.VARCHAR, 255, 0); // -> VARCHAR(255)","handlingStrategy":"validation","validationCode":"private static final java.util.regex.Pattern SAFE_TYPE =\n    java.util.regex.Pattern.compile(\"^[A-Za-z][A-Za-z0-9_]*(?:\\\\s+[A-Za-z][A-Za-z0-9_]*)*$\");\nstatic boolean isSafeH2TypeName(String t) {\n    return t == null || SAFE_TYPE.matcher(t).matches();\n}","typeGuard":"static String h2BaseTypeName(String t) {\n    if (t == null) return null;\n    int p = t.indexOf('(');\n    return (p >= 0) ? t.substring(0, p).trim() : t; // caller renders params from metadata\n}","tryCatchPattern":null,"preventionTips":["Pass only the base type name (no parentheses/params); renderMetadataType adds size/precision.","Treat a rejected type name as a signal to inspect driver metadata, not to bypass the guard.","Unit-test DDL round-trips against the H2 versions you support."],"tags":["h2","sql-guard","ddl","metadata","validation","type"],"backgroundTag":null,"analyzedSha":"5ee1e990e73fbcae1969dc554be254fedb3ab888","analyzedAt":"2026-08-14T07:05:03.077Z","schemaVersion":2},"datasetVersion":"2026-08-14T10:17:34.591Z"}