-
Notifications
You must be signed in to change notification settings - Fork 0
Add security utilities and implementation guide for Excel Tables (ListObjects) #28
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
…prehensive tests Co-authored-by: sbroenne <3026464+sbroenne@users.noreply.github.com>
…te SECURITY.md Co-authored-by: sbroenne <3026464+sbroenne@users.noreply.github.com>
Co-authored-by: sbroenne <3026464+sbroenne@users.noreply.github.com>
Dependency Review✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.Scanned FilesNone |
| catch (Exception ex) | ||
| { | ||
| throw new ArgumentException( | ||
| $"Error validating range: {ex.Message}", | ||
| parameterName, | ||
| ex); | ||
| } |
Check notice
Code scanning / CodeQL
Generic catch clause Note
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 28 days ago
To fix this problem, refactor the catch (Exception ex) block in ValidateRange so that only specific, anticipated exceptions are caught. In this context, the anticipated exceptions seem to be COMException (interop failure) and potentially ArgumentException (argument validation).
- Since the method already catches and rethrows
ArgumentException, the only "foreseeable" exception for interacting with the COM object would likely beCOMExceptionfrom Office interop. The generic catch-all is unnecessary unless particular runtime exceptions need handling (which should be enumerated individually if so). - Thus, simply remove the
catch (Exception ex)block. This allows unanticipated exceptions to propagate naturally, improving diagnosability. - No new imports or definitions are needed.
-
Copy modified line R70
| @@ -67,13 +67,7 @@ | ||
| // Re-throw ArgumentException as-is | ||
| throw; | ||
| } | ||
| catch (Exception ex) | ||
| { | ||
| throw new ArgumentException( | ||
| $"Error validating range: {ex.Message}", | ||
| parameterName, | ||
| ex); | ||
| } | ||
| // Removed catch (Exception ex) to avoid overly broad exception handling. | ||
| } | ||
|
|
||
| /// <summary> |
| catch (Exception ex) | ||
| { | ||
| return (false, $"Error validating range: {ex.Message}", 0, 0, 0); | ||
| } |
Check notice
Code scanning / CodeQL
Generic catch clause Note
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 28 days ago
To fix the issue, you should replace the generic catch (Exception ex) clause with catch clauses targeting specific exception types likely to occur when accessing dynamic COM properties. These could include COMException (when Excel interop fails), InvalidCastException (bad type on dynamic), and optionally RuntimeBinderException (from the C# dynamic runtime). Only these specific errors should be caught, to avoid hiding unexpected bugs. Any other exceptions will propagate and can be handled at a higher level if needed.
Edit only the catch clause in the file src/ExcelMcp.Core/Security/RangeValidator.cs within the TryValidateRange method (lines 114–117). You also need to add using Microsoft.CSharp.RuntimeBinder; if it is not already present at the top of the file, since you may now reference RuntimeBinderException explicitly.
-
Copy modified line R3 -
Copy modified line R115 -
Copy modified line R117 -
Copy modified lines R119-R126
| @@ -1,5 +1,6 @@ | ||
| using System; | ||
| using System.Runtime.InteropServices; | ||
| using Microsoft.CSharp.RuntimeBinder; | ||
|
|
||
| namespace Sbroenne.ExcelMcp.Core.Security; | ||
|
|
||
| @@ -111,10 +112,18 @@ | ||
|
|
||
| return (true, null, rowCount, colCount, cellCount); | ||
| } | ||
| catch (Exception ex) | ||
| catch (COMException ex) | ||
| { | ||
| return (false, $"Error validating range: {ex.Message}", 0, 0, 0); | ||
| return (false, $"COM error validating range: {ex.Message}", 0, 0, 0); | ||
| } | ||
| catch (InvalidCastException ex) | ||
| { | ||
| return (false, $"Cast error validating range: {ex.Message}", 0, 0, 0); | ||
| } | ||
| catch (RuntimeBinderException ex) | ||
| { | ||
| return (false, $"Binder error validating range: {ex.Message}", 0, 0, 0); | ||
| } | ||
| } | ||
|
|
||
| /// <summary> |
| foreach (char c in rangeAddress) | ||
| { | ||
| // Allow letters, digits, colon, dollar sign (for absolute references), exclamation (for sheet names) | ||
| if (!char.IsLetterOrDigit(c) && c != ':' && c != '$' && c != '!' && c != '_' && c != '.') | ||
| { | ||
| throw new ArgumentException( | ||
| $"Range address contains invalid character: '{c}'", | ||
| parameterName); | ||
| } | ||
| } |
Check notice
Code scanning / CodeQL
Missed opportunity to use Where Note
implicitly filters its target sequence
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 28 days ago
To fix the problem, the foreach loop on line 146 should be replaced by iteration over a filtered sequence that expresses the validation check as a filter predicate. Specifically, the code should iterate over only those characters that do not match the "allowed" character set, i.e., those where !char.IsLetterOrDigit(c) && c != ':' && c != '$' && c != '!' && c != '_' && c != '.'. For those, throw the exception, as before.
- Change lines 146-155: Replace the
foreach (char c in rangeAddress)loop together with the if-statement, with a LINQ.Where(...)loop. - Add a
using System.Linq;import at the top unless already present, so that.Whereworks. - Otherwise, keep all logic the same ( still throws exception for the first invalid character ).
- No changes needed elsewhere. Maintain all trim, length check, etc.
-
Copy modified line R3 -
Copy modified line R147 -
Copy modified lines R150-R152
| @@ -1,5 +1,6 @@ | ||
| using System; | ||
| using System.Runtime.InteropServices; | ||
| using System.Linq; | ||
|
|
||
| namespace Sbroenne.ExcelMcp.Core.Security; | ||
|
|
||
| @@ -143,15 +144,12 @@ | ||
| } | ||
|
|
||
| // Check for obviously invalid characters | ||
| foreach (char c in rangeAddress) | ||
| foreach (char c in rangeAddress.Where(c => !char.IsLetterOrDigit(c) && c != ':' && c != '$' && c != '!' && c != '_' && c != '.')) | ||
| { | ||
| // Allow letters, digits, colon, dollar sign (for absolute references), exclamation (for sheet names) | ||
| if (!char.IsLetterOrDigit(c) && c != ':' && c != '$' && c != '!' && c != '_' && c != '.') | ||
| { | ||
| throw new ArgumentException( | ||
| $"Range address contains invalid character: '{c}'", | ||
| parameterName); | ||
| } | ||
| throw new ArgumentException( | ||
| $"Range address contains invalid character: '{c}'", | ||
| parameterName); | ||
| } | ||
|
|
||
| return rangeAddress; |
| foreach (char c in tableName) | ||
| { | ||
| if (!char.IsLetterOrDigit(c) && c != '_' && c != '.') | ||
| { | ||
| throw new ArgumentException( | ||
| $"Table name contains invalid character: '{c}'. Only letters, numbers, underscores, and periods are allowed.", | ||
| parameterName); | ||
| } | ||
| } |
Check notice
Code scanning / CodeQL
Missed opportunity to use Where Note
implicitly filters its target sequence
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 28 days ago
To fix the issue, rewrite the loop to explicitly filter invalid characters using .Where(...). This reduces the nesting, makes the intent more apparent, and adheres to LINQ idioms. Specifically, replace the foreach (char c in tableName) { if (invalid) throw ...; } loop with a LINQ .Where filter selecting invalid characters, then iterate over those (if any) and throw on the first found. In this case, since the code should throw on the first invalid character, we can use .FirstOrDefault() (or .First()) to get any invalid char if one exists, or proceed if none is found.
File to change: src/ExcelMcp.Core/Security/TableNameValidator.cs
Region: Lines 80–89
Implementation plan:
- Replace the loop with:
- Use
var invalidChar = tableName.FirstOrDefault(c => !char.IsLetterOrDigit(c) && c != '_' && c != '.'); - If
invalidChar != '\0', throw theArgumentException(if the string is never empty, that's safe; otherwise check fortableName.Any()).
- Use
- Alternatively, if the string (possibly empty) could have
'\0'as a char, use.FirstOrDefault(...)with a nullable or other check. - No new imports needed;
using System.Linq;is already present.
-
Copy modified lines R81-R82 -
Copy modified lines R84-R86
| @@ -78,14 +78,12 @@ | ||
| } | ||
|
|
||
| // Validate characters (letters, numbers, underscores, periods only) | ||
| foreach (char c in tableName) | ||
| var invalidChar = tableName.FirstOrDefault(c => !char.IsLetterOrDigit(c) && c != '_' && c != '.'); | ||
| if (invalidChar != '\0') | ||
| { | ||
| if (!char.IsLetterOrDigit(c) && c != '_' && c != '.') | ||
| { | ||
| throw new ArgumentException( | ||
| $"Table name contains invalid character: '{c}'. Only letters, numbers, underscores, and periods are allowed.", | ||
| parameterName); | ||
| } | ||
| throw new ArgumentException( | ||
| $"Table name contains invalid character: '{invalidChar}'. Only letters, numbers, underscores, and periods are allowed.", | ||
| parameterName); | ||
| } | ||
|
|
||
| // Reserved names check (case-insensitive) |
Identified during architecture review of Issue #24: Excel Tables implementation requires security hardening against path traversal, injection attacks, DoS vectors, and COM memory leaks.
Security Utilities
TableNameValidator - Prevents injection via Excel table names:
RangeValidator - Prevents DoS from oversized ranges:
Implementation Guide
docs/EXCEL-TABLES-SECURITY-GUIDE.mdprovides patterns for Issue #24 implementers:PathValidator.ValidateExistingFile()usageHeaderRowRangeandDataBodyRangecan be null (header-only or no-data tables)Unlist()invalidates references - must release immediately to prevent leaksTest Coverage
Impact
Original prompt
This section details on the original issue you should resolve
<issue_title>🔴 CRITICAL: Security and Robustness Fixes Required Before Excel Tables Implementation</issue_title>
<issue_description>## 🚨 CRITICAL ISSUES - MUST FIX BEFORE ISSUE #24
These critical security and robustness issues were identified during senior architect review of the Excel Tables implementation plan (Issue #24). They must be addressed before proceeding with Excel Tables development.
Severity: 🔴 CRITICAL
Priority: P0 - Blocker
Blocks: Issue #24 (Excel Tables Implementation)
Issue 1: Path Traversal Vulnerability 🔴
Problem
TableCommands implementation does not use
PathValidatorfor file path validation, exposing path traversal vulnerability.Current Code (Vulnerable)
Required Fix
Impact
../../etc/passwd,C:\Windows\System32\config\SAM)List(),Create(),Rename(),Delete(),GetInfo()Acceptance Criteria
PathValidator.ValidateExistingFile()Issue 2: Null Reference - HeaderRowRange/DataBodyRange 🔴
Problem
HeaderRowRangeandDataBodyRangecan be NULL when tables have no headers or no data, causing NullReferenceException.Current Code (Vulnerable)
Required Fix
Impact
List(),GetInfo()Acceptance Criteria
Issue 3: COM Cleanup After Unlist() 🔴
Problem
ListObject.Unlist()removes the table but doesn't release the COM reference, causing memory leaks and potential use-after-free bugs.Current Code (Vulnerable)
Required Fix
Impact
✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.