Annotation header schema
Applies to templates in these 11 languages:
c (.c), cpp (.cpp, .cc, .cxx), go (.go), java (.java), javascript (.js, .mjs), perl (.pl), php (.php), python (.py), ruby (.rb), rust (.rs), shell (.sh).
It does not apply to YAML templates. The YAML engine deserializes the document and never reads comments — see YAML schema.
How the header is found
Section titled “How the header is found”cxg reads the first 50 lines of the file and looks for one line per
field. Every field is matched with this regex, with the field name
substituted for {}:
(?m)^[\s]*(?:#|//!?|\*)?[\s]*@{}[\s]*:[\s]*(.+?)[\s]*$In practice that accepts a line of any of these shapes, with any indentation, and takes the first match in the header:
# @id: my-template// @id: my-template//! @id: my-template* @id: my-template@id: my-templateA /* on the same line as the annotation does not match: the pattern
accepts #, //, //!, *, or no prefix at all, and nothing else. In a
C-style block comment, every annotation therefore has to sit on a
continuation line beginning with *.
Loaded by the pinned binary, from a file whose @id is on the opening /*
line and whose other annotations are on * lines. @name was read;
@id was not, so the id fell back to the filename:
$ cxg --disable-update-check --no-color template info block-comment[INF] Auto-update checks disabled
╔════════════════════════════════════════════════════════════════╗║ Template Information ║╚════════════════════════════════════════════════════════════════╝
ID: block-comment Name: Fixture whose header is a block comment Language: JavaScript Severity: High Author: docs.bugb.io generator Description: Puts one annotation on the /* line and the rest on * lines. Tags: fixture, javascript File: ./templates/block-comment.js Size: 272 bytesAn annotation whose value is empty is treated as absent. A line past the 50-line window is not read at all.
Fields
Section titled “Fields”| Annotation | Value | Required |
|---|---|---|
@id |
single value | yes |
@name |
single value | yes |
@author |
single value | yes |
@severity |
single value | yes |
@description |
single value | yes |
@version |
single value | no |
@tags |
comma-separated list | yes |
@cwe |
comma-separated list | no |
@cvss |
number | no |
@confidence |
integer | no |
@context_vars |
comma-separated list of tokens | no |
@vuln_class |
single value | no |
@hypothesis_tags |
comma-separated list | no |
@batch_group |
single value | no |
@auto_probe |
true, yes, 1 — anything else is false |
no |
“Required” is what
cxg template validate checks: a template
missing one still loads and still runs, with the fallbacks below. Nothing
refuses to run a template for a missing annotation.
Values in a comma-separated list are split on commas, trimmed, and lowercased. A single value is trimmed and kept as written.
A value for @cvss or @confidence that
does not parse as a number is discarded, and the annotation is treated as
absent. No warning is printed.
@severity
Section titled “@severity”| Written | Parsed as |
|---|---|
critical |
Critical |
high |
High |
medium |
Medium |
low |
Low |
info or informational |
Info |
| anything else | Medium |
Loaded by the pinned binary, from a template whose header says @severity: catastrophic:
$ cxg --disable-update-check --no-color template info schema-fixture-bogus-severity[INF] Auto-update checks disabled
╔════════════════════════════════════════════════════════════════╗║ Template Information ║╚════════════════════════════════════════════════════════════════╝
ID: schema-fixture-bogus-severity Name: Fixture with an unrecognised severity Language: Python Severity: Medium Author: docs.bugb.io generator Description: Declares a severity the parser does not recognise. Tags: fixture, python File: ./templates/bogus-severity.py Size: 285 bytesWhen an annotation is absent
Section titled “When an annotation is absent”cxg fills every field it needs, so a template with no header at all still loads under a name derived from its filename. Loaded by the pinned binary, from a file with no annotations:
$ cxg --disable-update-check --no-color template info schema-fixture-no-annotations[INF] Auto-update checks disabled
╔════════════════════════════════════════════════════════════════╗║ Template Information ║╚════════════════════════════════════════════════════════════════╝
ID: schema-fixture-no-annotations Name: schema fixture no annotations Language: Python Severity: Medium Author: Unknown Description: python template: schema-fixture-no-annotations Tags: python File: ./templates/schema-fixture-no-annotations.py Size: 57 bytesThe language tag is added to @tags whether or not the header declares
tags. The fallbacks themselves, from
src/engine/common.rs:
Show create_metadata, which fills every field
// Read file content for metadata parsing let content = std::fs::read_to_string(path).unwrap_or_default();
// Parse metadata from comment headers let parsed = parse_metadata_from_comments(&content);
// Check if metadata was found before moving fields let has_metadata = parsed.has_metadata();
// Fallback: derive ID from filename let fallback_id = path .file_stem() .and_then(|s| s.to_str()) .unwrap_or("unknown") .to_string();
// Use parsed values or fallbacks let id = parsed.id.unwrap_or_else(|| fallback_id.clone()); let name = parsed .name .unwrap_or_else(|| fallback_id.replace(['-', '_'], " ")); let author_name = parsed.author.unwrap_or_else(|| "Unknown".to_string()); let severity = parsed .severity .map(|s| parse_severity_string(&s)) .unwrap_or(Severity::Medium); let description = parsed .description .unwrap_or_else(|| format!("{} template: {}", language, fallback_id));
// Tags: use parsed tags, ensure language tag is always included let mut tags = parsed.tags; let lang_tag = language.to_string().to_lowercase(); if !tags.contains(&lang_tag) { tags.push(lang_tag); } // If no tags were found at all, just use language tag if tags.is_empty() { tags.push(language.to_string().to_lowercase()); }
// Log if metadata was found if has_metadata { tracing::debug!( "Parsed metadata from {}: id={}, tags={:?}", path.display(), id, tags ); }
TemplateMetadata { id, name, author: crate::types::AuthorInfo { name: author_name, email: None, github: None, }, severity, description, cve_ids: Vec::new(), cwe_ids: parsed.cwe, cvss_score: parsed.cvss, tags, language, file_path: path.to_path_buf(), created: chrono::Utc::now(), updated: chrono::Utc::now(), version: parsed.version.unwrap_or_else(|| "1.0.0".to_string()), confidence: parsed.confidence.or(Some(50)), context_vars: parsed .context_vars .iter() .map(|cv| { let name = if cv.is_array { format!("{}[]", cv.name) } else { cv.name.clone() }; let qualifier = if cv.required { "required" } else { "optional" }; format!("{}:{}", name, qualifier) }) .collect(), vuln_class: parsed.vuln_class, hypothesis_tags: parsed.hypothesis_tags, batch_group: parsed.batch_group, auto_probe: parsed.auto_probe, }@tags when you do not declare them
Section titled “@tags when you do not declare them”With no @tags line, cxg scrapes tags out of the code with these patterns.
Each is tried, and everything found is merged:
| Looks like | Pattern |
|---|---|
| Python/Ruby style - self.tags = […] or tags = […] | (?:self\.)?tags\s*=\s*\[([^\]]+)\] |
| JavaScript/JSON style - tags: […] | tags\s*:\s*\[([^\]]+)\] |
@context_vars
Section titled “@context_vars”A single context variable declaration parsed from @context_vars. Format in header: name:required or name[]:optional The [] suffix indicates the variable is a JSON array at runtime.
| Token | Meaning |
|---|---|
name:required |
the template cannot operate without it |
name:optional |
used when present |
name[]:required |
the value is a JSON array at runtime |
name |
no qualifier: optional |
Qualifiers are case-insensitive, and required, req and r all mean
required. Anything else means optional. Values reach the template as JSON in
CERT_X_GEN_CONTEXT — see
How cxg executes templates.
What cxg template validate says about a missing header
Section titled “What cxg template validate says about a missing header”$ cxg --disable-update-check --no-color template validate templates/schema-fixture-no-annotations.py[INF] Auto-update checks disabled════════════════════════════════════════════════════════════════════════════════CERT-X-GEN Template Validator════════════════════════════════════════════════════════════════════════════════
Found 1 template(s) to validate
✓ schema-fixture-no-annotations.py [warning] 3: common.missing_target_host: Template does not appear to use CERT_X_GEN_TARGET_HOST or target variables. Templates should be dynamic. Add: HOST = os.environ.get('CERT_X_GEN_TARGET_HOST') or similar [warning] 1: common.missing_metadata: Template is missing metadata annotations. Add @field: annotations at the top of the file. Required: @id, @name, @author, @severity, @description, @tags [warning] 1: enhanced.missing_network_code: Template does not appear to have network/socket code. Security templates typically need network connectivity. Suggestion: import socket [warning] 1: enhanced.missing_error_handling: No error handling detected. Templates should handle errors gracefully to avoid crashes during scanning. Suggestion: try: # codeexcept Exception as e: pass [info] 1: enhanced.no_entry_point: No main() function or __name__ guard found. Recommendation: def main(): pass
if __name__ == '__main__': main() [info] schema.no_findings_array: No 'findings' array detected. CERT-X-GEN expects output with a 'findings' array. Structure: {"findings": [{...}], "metadata": {...}} [info] schema.missing_template_id: Required Finding field 'template_id' not found in template. Ensure findings include this field. [info] schema.missing_template_name: Required Finding field 'template_name' not found in template. Ensure findings include this field. [info] schema.missing_severity: Required Finding field 'severity' not found in template. Ensure findings include this field. [info] schema.missing_host: Required Finding field 'host' not found in template. Ensure findings include this field. [info] schema.missing_matched_at: Required Finding field 'matched_at' not found in template. Ensure findings include this field.
════════════════════════════════════════════════════════════════════════════════Validation Summary════════════════════════════════════════════════════════════════════════════════
Total Templates: 1 ✓ Passed: 1 ✗ Failed: 0 Success Rate: 100%
