target-authoring — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited target-authoring (Agent Skill) and scored it 45/100 (orange). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 7 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 7 flagged
A base64 string of 128+ characters appears in a documentation file. Encoded prompt injection hides the hostile instruction in base64 — invisible to keyword filters — and relies on the agent's ability to decode it at runtime. There is no normal authoring reason to embed a multi-hundred-byte base64 blob in skill docs.
*.sig, SIGNATURES) outside the documentation.A base64 string of 128+ characters appears in a documentation file. Encoded prompt injection hides the hostile instruction in base64 — invisible to keyword filters — and relies on the agent's ability to decode it at runtime. There is no normal authoring reason to embed a multi-hundred-byte base64 blob in skill docs.
*.sig, SIGNATURES) outside the documentation.A base64 string of 128+ characters appears in a documentation file. Encoded prompt injection hides the hostile instruction in base64 — invisible to keyword filters — and relies on the agent's ability to decode it at runtime. There is no normal authoring reason to embed a multi-hundred-byte base64 blob in skill docs.
*.sig, SIGNATURES) outside the documentation.A base64 string of 128+ characters appears in a documentation file. Encoded prompt injection hides the hostile instruction in base64 — invisible to keyword filters — and relies on the agent's ability to decode it at runtime. There is no normal authoring reason to embed a multi-hundred-byte base64 blob in skill docs.
*.sig, SIGNATURES) outside the documentation.A base64 string of 128+ characters appears in a documentation file. Encoded prompt injection hides the hostile instruction in base64 — invisible to keyword filters — and relies on the agent's ability to decode it at runtime. There is no normal authoring reason to embed a multi-hundred-byte base64 blob in skill docs.
*.sig, SIGNATURES) outside the documentation.A base64 string of 128+ characters appears in a documentation file. Encoded prompt injection hides the hostile instruction in base64 — invisible to keyword filters — and relies on the agent's ability to decode it at runtime. There is no normal authoring reason to embed a multi-hundred-byte base64 blob in skill docs.
*.sig, SIGNATURES) outside the documentation.A base64 string of 128+ characters appears in a documentation file. Encoded prompt injection hides the hostile instruction in base64 — invisible to keyword filters — and relies on the agent's ability to decode it at runtime. There is no normal authoring reason to embed a multi-hundred-byte base64 blob in skill docs.
*.sig, SIGNATURES) outside the documentation.Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Canonical patterns from Microsoft.Common.CurrentVersion.targets in the MSBuild repository.
Every major entry point (Build, Rebuild, Clean) delegates to a property listing its dependencies, which chains through Before → Core → After:
<PropertyGroup>
<BuildDependsOn>
BeforeBuild;
CoreBuild;
AfterBuild
</BuildDependsOn>
</PropertyGroup>
<Target Name="Build"
Condition=" '$(_InvalidConfigurationWarning)' != 'true' "
DependsOnTargets="$(BuildDependsOn)"
Returns="@(TargetPathWithTargetPlatformMoniker)" />
<!-- Empty extensibility targets — users override these -->
<Target Name="BeforeBuild" />
<Target Name="AfterBuild" />CoreBuild delegates to $(CoreBuildDependsOn) and includes error handlers:
<Target Name="CoreBuild" DependsOnTargets="$(CoreBuildDependsOn)">
<OnError ExecuteTargets="_TimeStampAfterCompile;PostBuildEvent"
Condition="'$(RunPostBuildEvent)' == 'Always'" />
<OnError ExecuteTargets="_CleanRecordFileWrites" />
</Target>DependsOnTargets="$(MyTargetDependsOn)"), not hardcoded targets.OnError goes inside the orchestrating target to ensure cleanup runs even on failure.When adding a custom target to an existing chain, append to the DependsOn property:
<!-- GOOD: Append to existing chain -->
<PropertyGroup>
<CompileDependsOn>$(CompileDependsOn);MyCodeGenTarget</CompileDependsOn>
</PropertyGroup>
<!-- BAD: Overwrites the entire chain, dropping SDK targets -->
<PropertyGroup>
<CompileDependsOn>MyCodeGenTarget</CompileDependsOn>
</PropertyGroup>| Mechanism | Defined in | Best for |
|---|---|---|
DependsOnTargets | The target that needs deps | Target explicitly requires others |
BeforeTargets | The injecting target | Insert before a target you don't own |
AfterTargets | The injecting target | Insert after a target you don't own |
Validation targets use BeforeTargets to intercept all entry points:
<Target Name="_CheckForInvalidConfigurationAndPlatform"
BeforeTargets="$(BuildDependsOn);Build;$(RebuildDependsOn);Rebuild;$(CleanDependsOn);Clean">
</Target>Rules:
DependsOnTargets when your target needs specific prerequisites.BeforeTargets/AfterTargets when injecting into a pipeline you don't own.BeforeTargets="CoreCompile" over modifying $(CompileDependsOn) when you don't control the targets file.<!-- Build returns items for consumption by referencing projects -->
<Target Name="Build"
DependsOnTargets="$(BuildDependsOn)"
Returns="@(TargetPathWithTargetPlatformMoniker)" />
<!-- GetTargetPath is a lightweight query target -->
<Target Name="GetTargetPath" Returns="@(TargetPathWithTargetPlatformMoniker)" />GetTargetPath, GetTargetFrameworks) should use Returns, not Outputs.| Pattern | Meaning | Example |
|---|---|---|
_PrefixedName | Internal/private target | _TimeStampBeforeCompile |
CoreXxx | The actual implementation | CoreBuild, CoreCompile |
BeforeXxx / AfterXxx | Empty extensibility hooks | BeforeBuild, AfterCompile |
PrepareXxx | Setup/validation phase | PrepareForBuild |
ResolveXxx | Discovery/resolution phase | ResolveReferences |
GetXxx | Lightweight query (no side effects) | GetTargetPath |
<!-- 1. Define the DependsOn chain for extensibility -->
<PropertyGroup>
<MyFeatureDependsOn>
_ValidateMyFeatureInputs;
BeforeMyFeature;
CoreMyFeature;
AfterMyFeature
</MyFeatureDependsOn>
</PropertyGroup>
<!-- 2. Outer target with Returns for inter-project communication -->
<Target Name="MyFeature"
DependsOnTargets="$(MyFeatureDependsOn)"
Returns="@(MyFeatureOutput)" />
<!-- 3. Empty extensibility points -->
<Target Name="BeforeMyFeature" />
<Target Name="AfterMyFeature" />
<!-- 4. Core implementation with Inputs/Outputs for incrementality -->
<Target Name="CoreMyFeature"
Inputs="$(MSBuildAllProjects);@(MyFeatureInput)"
Outputs="$(IntermediateOutputPath)myfeature.generated.cs">
<Exec Command="my-tool.exe -o $(IntermediateOutputPath)myfeature.generated.cs" />
<!-- 5. Register outputs for clean tracking -->
<ItemGroup>
<Compile Include="$(IntermediateOutputPath)myfeature.generated.cs" />
<FileWrites Include="$(IntermediateOutputPath)myfeature.generated.cs" />
</ItemGroup>
</Target>
<!-- 6. Validation target runs first in the dependency chain -->
<Target Name="_ValidateMyFeatureInputs">
<Error Text="MyFeatureInput items are required."
Condition="'@(MyFeatureInput)' == ''" />
</Target>$(ExistingProperty) when appending.Returns.BeforeTargets on SDK targets have nothing to hook into yet. Move targets to .targets.~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.