error-pattern-safety

oleh github

Pedoman Keamanan Pola Kesalahan untuk Mesin Agen

npx skills add https://github.com/github/gh-aw --skill error-pattern-safety

Error Pattern Safety Guidelines

Use these regex safety rules in agentic engines to prevent JavaScript infinite loops.

The Problem

With the JavaScript global flag (/pattern/g), zero-width matches can cause infinite loops because:

  1. JavaScript's regex.exec() with the g flag uses lastIndex to track position
  2. When a pattern matches zero-width, lastIndex doesn't advance
  3. The same position is matched repeatedly, causing an infinite loop

Dangerous Pattern Examples

❌ NEVER USE THESE PATTERNS:

// Pure .* - matches everything including empty string at end
/.*/g

// Single character with * - matches zero or more (including zero)
/a*/g

// Patterns that can match empty string
/(x|y)*/g

Safe Pattern Examples

✅ ALWAYS USE PATTERNS LIKE THESE:

// Required prefix before .*
/error.*/gi
/error.*permission.*denied/gi

// Specific structure with required content
/\[(\d{4}-\d{2}-\d{2})\]\s+(ERROR):\s+(.+)/g

// Required characters throughout
/access denied.*user.*not authorized/gi

Pattern Safety Rules

  1. Always require at least one character match

    • Use .+ instead of .* when you need "something"
    • Ensure pattern has required prefix/suffix
  2. Never use bare .* as the entire pattern

    • Always combine with required text: error.*
    • Never just .* or .*?
  3. Test patterns against empty string

    const regex = /your-pattern/g;
    if (regex.test("")) {
      throw new Error("Pattern matches empty string - DANGEROUS!");
    }
    
  4. Use specific anchors when possible

    • Start: ^error.*
    • End: .*error$
    • Word boundaries: \berror\b

Validation Tests

All error patterns must pass the same safety checks used by the repo’s unit suite:

Go tests

// Test that pattern doesn't match empty string
func TestPatternSafety(t *testing.T) {
    pattern := "your-pattern"
    regex := regexp.MustCompile(pattern)

    if regex.MatchString("") {
        t.Error("Pattern matches empty string!")
    }
}

Run the relevant package tests with make test-unit.

JavaScript tests

test("should not match empty string", () => {
  const regex = new RegExp("your-pattern", "g");
  expect(regex.test("")).toBe(false);
});

Use the relevant *.test.cjs suite under actions/setup/js/ or pkg/workflow/js/ for the area you changed, or run the repo’s JavaScript checks via make test-js.

Safety Mechanisms in the validation layer

The repo’s validation helpers include built-in protections for dangerous regex patterns:

  1. Zero-width detection: Checks whether a regex stops advancing across iterations
  2. Iteration warning: Warns when repeated runs approach a hang threshold
  3. Hard limit: Stops execution before runaway loops can lock the process
if (regex.lastIndex === lastIndex) {
  core.error(`Infinite loop detected! Pattern: ${pattern.pattern}`);
  break;
}

Adding New Error Patterns

When adding new error patterns to engines:

  1. Write the pattern with required content

    {
        Pattern:      `(?i)error.*permission.*denied`,
        LevelGroup:   0,
        MessageGroup: 0,
        Description:  "Permission denied error",
    }
    
  2. Test against empty string

    • Run: make test-unit
    • Checks: TestAllEnginePatternsSafe
  3. Test with actual log samples

    • Ensure it matches real errors
    • Ensure it doesn't match informational text
  4. Document the pattern

    • Add clear description
    • Note what it's designed to catch

Pattern Conversion: Go to JavaScript

Patterns are converted from Go to JavaScript:

// Go pattern (case-insensitive flag)
Pattern: `(?i)error.*permission.*denied`

// Converted to JavaScript
new RegExp("error.*permission.*denied", "gi")

The (?i) prefix is removed because JavaScript uses the i flag instead.

Examples from Current Codebase

✅ Safe Patterns

// Requires "error" prefix
Pattern: `(?i)error.*permission.*denied`

// Requires specific timestamp format
Pattern: `(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z)\s+\[(ERROR)\]\s+(.+)`

// Requires "access denied" prefix
Pattern: `(?i)access denied.*user.*not authorized`

How to Fix Unsafe Patterns

If you find a pattern that matches empty string:

Before (unsafe):

Pattern: `.*error.*`  // Can match empty at start/end

After (safe):

Pattern: `error.*`     // Requires "error" at start
// OR
Pattern: `.*error.+`   // Requires "error" and at least one char after
// OR
Pattern: `\berror\b.*` // Requires word "error"

Testing Checklist

Before committing pattern changes:

  • Run make test-unit
  • Verify the relevant engine error-pattern tests still pass
  • Run the JavaScript checks for the changed area with make test-js or the targeted Vitest suite
  • Verify the pattern matches intended error messages
  • Verify the pattern does not match informational text or empty-string edge cases

References