Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions .github/instructions/pre-commit-testing.instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
---
applyTo:
- "**"
---

# Pre-Commit Testing Requirements
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sorry, what I meant was to amend this one.

https://github.com/microsoft/kiota/blob/main/.github/instructions/writer-literal-security.instructions.md

that one

https://github.com/microsoft/kiota/blob/main/.github/skills/codegen-literal-security-scan/SKILL.md

this one

https://github.com/microsoft/kiota/blob/main/.github/agents/codegen-security-guardian.agent.md

And I think the reason why they didn't get looped in is because we don't have .github/copilot-instructions.md on this repository. (contrary to other repositories I work on)


Before creating any git commit, agents **must** run the relevant tests and verify they pass.

## Rules

1. **Always run tests before committing.** Never commit code that has not been validated by running the relevant test suite.
2. **Scope tests appropriately.** Run at minimum the tests related to the files you changed. If unsure which tests cover your changes, run the full test project that contains the modified code.
3. **Fix failing tests before committing.** If tests fail, diagnose and fix the issue. Do not commit with known test failures unless explicitly instructed by the user.
4. **Re-run tests after fixing failures.** After making corrections, run tests again to confirm they pass.

## Test Commands

| Project | Command |
|---------|---------|
| Kiota.Builder | `dotnet test tests/Kiota.Builder.Tests/Kiota.Builder.Tests.csproj` |
| VS Code Extension | `cd vscode/packages && npm test` |

Use `--filter "FullyQualifiedName~ClassName"` to scope .NET tests to specific test classes when a full run is unnecessary.
2 changes: 1 addition & 1 deletion src/Kiota.Builder/KiotaBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2082,7 +2082,7 @@ private static void SetEnumOptions(IOpenApiSchema schema, CodeEnum target)
SerializationName = x,
Documentation = new()
{
DescriptionTemplate = optionDescription?.Description ?? string.Empty,
DescriptionTemplate = optionDescription?.Description?.CleanupDescription() ?? string.Empty,
},
};
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,13 @@ public override string GetTypeString(CodeTypeBase code, CodeElement targetElemen
throw new InvalidOperationException($"type of type {code.GetType()} is unknown");
}
#pragma warning restore CA1822 // Method should be static
internal static string RemoveInvalidDescriptionCharacters(string originalDescription) => originalDescription.Replace("\\", "/", StringComparison.OrdinalIgnoreCase).Replace("\"\"\"", "\\\"\\\"\\\"", StringComparison.OrdinalIgnoreCase);
internal static string RemoveInvalidDescriptionCharacters(string originalDescription) =>
string.IsNullOrEmpty(originalDescription) ? string.Empty :
originalDescription.Replace("\\", "/", StringComparison.OrdinalIgnoreCase)
.Replace("\t", " ", StringComparison.OrdinalIgnoreCase)
.Replace("\r", " ", StringComparison.OrdinalIgnoreCase)
.Replace("\n", " ", StringComparison.OrdinalIgnoreCase)
.Replace("\"\"\"", "\\\"\\\"\\\"", StringComparison.OrdinalIgnoreCase);
public override string TranslateType(CodeType type)
{
ArgumentNullException.ThrowIfNull(type);
Expand Down
65 changes: 65 additions & 0 deletions tests/Kiota.Builder.Tests/Writers/Python/CodeEnumWriterTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,4 +66,69 @@ public void EscapesEnumWireValues()
var result = tw.ToString();
Assert.Contains("Option1 = \"line1\\\"\\nline2\",", result);
}
[Fact]
public void SanitizesEnumOptionDescriptionWithNewlines()
{
currentEnum.AddOption(new CodeEnumOption
{
Name = "Option1",
SerializationName = "option1",
Documentation = new()
{
DescriptionTemplate = "line1\nimport os; os.system('evil')\r\nline3",
},
});
writer.Write(currentEnum);
var result = tw.ToString();
Assert.Contains("Option1 = \"option1\"", result);
// Verify no injected code appears as executable Python (on its own line outside a comment)
var lines = result.Split('\n').Select(l => l.TrimEnd('\r')).Where(l => !string.IsNullOrWhiteSpace(l)).ToArray();
foreach (var line in lines)
{
var trimmed = line.TrimStart();
// Every line must be a comment, import, class declaration, or assignment — never bare injected code
Assert.True(
trimmed.StartsWith('#') || trimmed.StartsWith("from ") || trimmed.StartsWith("class ") || trimmed.Contains('=') || trimmed == "pass",
$"Unexpected executable line in output: {trimmed}");
}
}
[Fact]
public void SanitizesEnumOptionDescriptionWithTripleQuotes()
{
currentEnum.AddOption(new CodeEnumOption
{
Name = "Option1",
SerializationName = "option1",
Documentation = new()
{
DescriptionTemplate = "before\"\"\"\nimport os\nafter",
},
});
writer.Write(currentEnum);
var result = tw.ToString();
Assert.Contains("Option1 = \"option1\"", result);
// Verify no injected code appears as executable Python
var lines = result.Split('\n').Select(l => l.TrimEnd('\r')).Where(l => !string.IsNullOrWhiteSpace(l)).ToArray();
foreach (var line in lines)
{
var trimmed = line.TrimStart();
Assert.True(
trimmed.StartsWith('#') || trimmed.StartsWith("from ") || trimmed.StartsWith("class ") || trimmed.Contains('=') || trimmed == "pass",
$"Unexpected executable line in output: {trimmed}");
}
// Verify no unescaped triple quotes that could break out of a docstring
Assert.DoesNotContain("\"\"\"\nimport", result);
}
[Theory]
[InlineData("line1\nimport os\nline3", "line1 import os line3")]
[InlineData("line1\r\nimport os\r\nline3", "line1 import os line3")]
[InlineData("before\"\"\"\nimport os\nafter", "before\\\"\\\"\\\" import os after")]
[InlineData("normal description", "normal description")]
[InlineData("", "")]
[InlineData(null, "")]
public void RemoveInvalidDescriptionCharactersHandlesInjection(string input, string expected)
{
var result = Kiota.Builder.Writers.Python.PythonConventionService.RemoveInvalidDescriptionCharacters(input!);
Assert.Equal(expected, result);
}
}
Loading