Skip to content

SARIF invocation properties from InvocationContext #1795

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

Open
KalleOlaviNiemitalo opened this issue Jul 16, 2022 · 3 comments
Open

SARIF invocation properties from InvocationContext #1795

KalleOlaviNiemitalo opened this issue Jul 16, 2022 · 3 comments

Comments

@KalleOlaviNiemitalo
Copy link

I have a tool that uses System.CommandLine and can create a Static Analysis Results Interchange Format (SARIF) Version 2.1.0 log file. SARIF defines the invocation.commandLine, invocation.arguments, and invocation.responseFiles properties in which the tool can save information about how it was invoked. I'd like to implement that in the tool. I am not requesting any changes in System.CommandLine for this purpose, but I am curious if anyone else has implemented something similar.

It would be easy to use string[] args from the Main method for the SARIF invocation.arguments property, and Environment.CommandLine for the SARIF invocation.commandLine property. However, I hope I can instead get the information from System.CommandLine somehow, for these reasons:

  • System.CommandLine parses the response file syntax, and I'd rather not reimplement that parsing just for the sake of the SARIF invocation.responseFiles property, especially if the syntax can change later (Does not allow arguments that start with the '@' character #1625).
  • If the command line contains passwords or other sensitive information, then those should be redacted from the log file. I hope I can somehow mark the Argument<T> and Option<T> instances as sensitive and then redact the values if SymbolResult.Symbol is so marked. I can imagine several ways to mark them:
    • During initialization, create a List<Symbol> that contains all the sensitive Argument<T> and Option<T> instances.
    • Derive classes SensitiveArgument<T> and SensitiveOption<T>. Having to forward the constructors would be a bit annoying.
    • Define struct SensitiveString, and use Argument<SensitiveString> and Option<SensitiveString>. (Alternatively SecureString, but that is cumbersome to use and no longer recommended.)
    • Add a marker ICompletionSource to Argument.Completions. That would be somewhat weird.

For the SARIF invocation.arguments property, there are ParseResult.Directives and ParseResult.Tokens, which would let the tool check the sensitivity markers, but I cannot fully reconstruct the original string[] args from them:

  • The order of directives is not preserved. I suppose that wouldn't be a problem in practice, but it feels a bit dirty.
  • No public way to distinguish between --option=value, --option:value, and --option value. Non-public reflection could perhaps do it by checking whether both tokens have the same Token.Position. As above, the difference shouldn't matter in practice.
  • No obvious way to know which tokens came from response files, or which response files were even used. Could be worked around by disabling response files. (The tool doesn't need very long command lines because it reads configuration files.)

So, it seems I can make the tool log a invocation.arguments property that is good enough although not quite perfect, but I cannot make it log invocation.commandLine (too difficult to redact) nor invocation.responseFiles (too difficult to find which files were used, and the tokens from the files will be in invocation.arguments).

@KalleOlaviNiemitalo
Copy link
Author

I got the redaction almost working. I defined struct RedactableString with a public RedactableString(string value) constructor, and System.CommandLine is able to convert arguments to that. The app first collects directives from InvocationContext.ParseResult.Tokens, and then recursively collects other args from InvocationContext.ParseResult.RootCommandResult:

  • If a CommandResult, then collect CommandResult.Token and recursively SymbolResult.Children.
  • If an OptionResult without OptionResult.IsImplicit, then collect OptionResult.Token and recursively SymbolResult.Children. Any child should be an ArgumentResult. After collecting args from the children, convert to the "--option=value" syntax if appropriate.
  • If an ArgumentResult, then collect SymbolResult.Tokens and redact according to ArgumentResult.Argument.ValueType. This covers arguments of options, too.

Redact if Argument.ValueType is RedactableString or assignable to IEnumerable<RedactableString>.

The remaining difficulty is how to place "--" at the correct location. It is listed in InvocationContext.ParseResult.Tokens but AFAICT nowhere in InvocationContext.ParseResult.RootCommandResult. Normally, I'd inject "--" before the first argument that starts with "-" and does not belong to an option, but I cannot do that here because System.CommandLine parses "--foo" as an argument if no "--foo" option is defined, and it allows options after that argument.

Can I rely on SymbolResult.Tokens referring to the same Token instances that are in ParseResult.Tokens? If so, I can use the SymbolResult instances to collect the redaction information to a reference-equality dictionary and then enumerate ParseResult.Tokens and redact each token according to the dictionary.

@KalleOlaviNiemitalo
Copy link
Author

I now have it working, based on reference equality of Token instances.

@KalleOlaviNiemitalo
Copy link
Author

I wouldn't need to rely on reference equality if Token.Position were public.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant