From 9a6c24ffe57c32d9018ef678301cea622c9004bc Mon Sep 17 00:00:00 2001 From: pierre-loup-tristant-sonarsource Date: Fri, 8 Nov 2024 14:29:25 +0000 Subject: [PATCH 1/4] Create rule S7147 --- rules/S7147/metadata.json | 2 ++ rules/S7147/secrets/metadata.json | 56 +++++++++++++++++++++++++++++++ rules/S7147/secrets/rule.adoc | 44 ++++++++++++++++++++++++ 3 files changed, 102 insertions(+) create mode 100644 rules/S7147/metadata.json create mode 100644 rules/S7147/secrets/metadata.json create mode 100644 rules/S7147/secrets/rule.adoc diff --git a/rules/S7147/metadata.json b/rules/S7147/metadata.json new file mode 100644 index 00000000000..2c63c085104 --- /dev/null +++ b/rules/S7147/metadata.json @@ -0,0 +1,2 @@ +{ +} diff --git a/rules/S7147/secrets/metadata.json b/rules/S7147/secrets/metadata.json new file mode 100644 index 00000000000..e94a78c9e88 --- /dev/null +++ b/rules/S7147/secrets/metadata.json @@ -0,0 +1,56 @@ +{ + "title": "Atlassian secrets should not be disclosed", + "type": "VULNERABILITY", + "code": { + "impacts": { + "SECURITY": "HIGH" + }, + "attribute": "TRUSTWORTHY" + }, + "status": "ready", + "remediation": { + "func": "Constant\/Issue", + "constantCost": "30min" + }, + "tags": [ + "cwe", + "cert" + ], + "defaultSeverity": "Blocker", + "ruleSpecification": "RSPEC-7147", + "sqKey": "S7147", + "scope": "All", + "securityStandards": { + "CWE": [ + 798, + 259 + ], + "OWASP": [ + "A3" + ], + "CERT": [ + "MSC03-J." + ], + "OWASP Top 10 2021": [ + "A7" + ], + "PCI DSS 3.2": [ + "6.5.10" + ], + "PCI DSS 4.0": [ + "6.2.4" + ], + "ASVS 4.0": [ + "2.10.4", + "3.5.2", + "6.4.1" + ], + "STIG ASD_V5R3": [ + "V-222642" + ] + }, + "defaultQualityProfiles": [ + "Sonar way" + ], + "quickfix": "unknown" +} diff --git a/rules/S7147/secrets/rule.adoc b/rules/S7147/secrets/rule.adoc new file mode 100644 index 00000000000..eb5a46d7cc9 --- /dev/null +++ b/rules/S7147/secrets/rule.adoc @@ -0,0 +1,44 @@ + +include::../../../shared_content/secrets/description.adoc[] + +== Why is this an issue? + +include::../../../shared_content/secrets/rationale.adoc[] + +If attackers gain access to Atlassian API tokens or OAuth credentials, they will be able to interact with Atlassian product APIs on behalf of the compromised account. This includes products such as Jira, Confluence, or BitBucket. + +=== What is the potential impact? + +Below are some real-world scenarios that illustrate some impacts of an attacker +exploiting the secret. + +include::../../../shared_content/secrets/impact/source_code_compromise.adoc[] + +include::../../../shared_content/secrets/impact/supply_chain_attack.adoc[] + +include::../../../shared_content/secrets/impact/data_compromise.adoc[] + +include::../../../shared_content/secrets/impact/data_modification.adoc[] + +== How to fix it + +include::../../../shared_content/secrets/fix/revoke.adoc[] + +include::../../../shared_content/secrets/fix/vault.adoc[] + +=== Code examples + +:example_secret: ATATT3xFfGF0fMvPEsrw8suA4pUbNn7Ke4ymCtbDUUia0OuNj4Dj_c_z-4YnGzP3_uToXP2HUU9DX3DZhkF1VoF14QyiXMZ1y7FIxVmzc-RStczBTs2640JgH4BjAdpfiSkgrF8Qv0XShGg9DlYekSbLqSLQ2db3qfTzqUoDLPgjZu-b49SE=D65AD736 +:example_name: atlassian.api-token +:example_env: ATLASSIAN_API_TOKEN + +include::../../../shared_content/secrets/examples.adoc[] + +== Resources + +=== Documentation + +* Atlassian Support - https://confluence.atlassian.com/enterprise/using-personal-access-tokens-1026032365.html[Using personal access tokens] +* Atlassian Support - https://developer.atlassian.com/cloud/jira/platform/oauth-2-3lo-apps/[OAuth 2.0 (3LO) apps] + +include::../../../shared_content/secrets/resources/standards.adoc[] From eaec3d0cd86d5d897c8c5730c9767f18619424c0 Mon Sep 17 00:00:00 2001 From: Pierre-Loup <49131563+pierre-loup-tristant-sonarsource@users.noreply.github.com> Date: Tue, 12 Nov 2024 17:39:28 +0100 Subject: [PATCH 2/4] Update rules/S7147/secrets/rule.adoc Co-authored-by: Sebastien Andrivet <138577785+sebastien-andrivet-sonarsource@users.noreply.github.com> --- rules/S7147/secrets/rule.adoc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rules/S7147/secrets/rule.adoc b/rules/S7147/secrets/rule.adoc index eb5a46d7cc9..fb66fda9ed1 100644 --- a/rules/S7147/secrets/rule.adoc +++ b/rules/S7147/secrets/rule.adoc @@ -34,6 +34,9 @@ include::../../../shared_content/secrets/fix/vault.adoc[] include::../../../shared_content/secrets/examples.adoc[] +=== Going the extra mile + +include::../../../shared_content/secrets/extra_mile/permissions_scope.adoc[] == Resources === Documentation From eb0de6c0439afd7c91f48cad3aa630def1a98c70 Mon Sep 17 00:00:00 2001 From: Pierre-Loup Date: Wed, 13 Nov 2024 09:52:42 +0100 Subject: [PATCH 3/4] Merge branch 'master' into rule/add-RSPEC-S7147 --- rules/S6667/metadata.json | 2 +- rules/S6668/metadata.json | 2 +- rules/S6669/metadata.json | 2 +- rules/S6670/metadata.json | 2 +- rules/S6672/metadata.json | 2 +- rules/S6674/metadata.json | 2 +- rules/S6675/metadata.json | 2 +- rules/S6776/metadata.json | 2 +- rules/S6798/csharp/metadata.json | 2 +- rules/S6800/csharp/metadata.json | 2 +- rules/S6869/kubernetes/metadata.json | 2 +- rules/S6930/metadata.json | 2 +- rules/S6931/metadata.json | 2 +- rules/S6932/csharp/metadata.json | 2 +- rules/S6934/metadata.json | 2 +- rules/S6960/metadata.json | 2 +- rules/S6964/metadata.json | 2 +- rules/S6967/metadata.json | 2 +- rules/S6968/csharp/metadata.json | 2 +- rules/S7018/docker/metadata.json | 4 +- rules/S7019/docker/metadata.json | 2 +- rules/S7019/docker/rule.adoc | 10 +- rules/S7020/docker/metadata.json | 4 +- rules/S7021/docker/metadata.json | 2 +- rules/S7023/docker/metadata.json | 2 +- rules/S7026/docker/metadata.json | 2 +- rules/S7030/docker/metadata.json | 2 +- rules/S7031/docker/metadata.json | 4 +- rules/S7116/cfamily/metadata.json | 24 ++++ rules/S7116/cfamily/rule.adoc | 152 ++++++++++++++++++++ rules/S7116/metadata.json | 2 + rules/S7132/cfamily/metadata.json | 27 ++++ rules/S7132/cfamily/rule.adoc | 136 ++++++++++++++++++ rules/S7132/metadata.json | 2 + rules/S7144/metadata.json | 2 + rules/S7144/secrets/metadata.json | 56 ++++++++ rules/S7144/secrets/rule.adoc | 62 ++++++++ rules/S7146/metadata.json | 2 + rules/S7146/secrets/metadata.json | 56 ++++++++ rules/S7146/secrets/rule.adoc | 43 ++++++ rules/S7148/metadata.json | 2 + rules/S7148/secrets/metadata.json | 56 ++++++++ rules/S7148/secrets/rule.adoc | 35 +++++ rules/S7149/metadata.json | 2 + rules/S7149/secrets/metadata.json | 56 ++++++++ rules/S7149/secrets/rule.adoc | 34 +++++ shared_content/secrets/impact/phishing.adoc | 2 +- 47 files changed, 787 insertions(+), 34 deletions(-) create mode 100644 rules/S7116/cfamily/metadata.json create mode 100644 rules/S7116/cfamily/rule.adoc create mode 100644 rules/S7116/metadata.json create mode 100644 rules/S7132/cfamily/metadata.json create mode 100644 rules/S7132/cfamily/rule.adoc create mode 100644 rules/S7132/metadata.json create mode 100644 rules/S7144/metadata.json create mode 100644 rules/S7144/secrets/metadata.json create mode 100644 rules/S7144/secrets/rule.adoc create mode 100644 rules/S7146/metadata.json create mode 100644 rules/S7146/secrets/metadata.json create mode 100644 rules/S7146/secrets/rule.adoc create mode 100644 rules/S7148/metadata.json create mode 100644 rules/S7148/secrets/metadata.json create mode 100644 rules/S7148/secrets/rule.adoc create mode 100644 rules/S7149/metadata.json create mode 100644 rules/S7149/secrets/metadata.json create mode 100644 rules/S7149/secrets/rule.adoc diff --git a/rules/S6667/metadata.json b/rules/S6667/metadata.json index 36ae77f81d5..d2365a124c3 100644 --- a/rules/S6667/metadata.json +++ b/rules/S6667/metadata.json @@ -3,7 +3,7 @@ "type": "CODE_SMELL", "code": { "impacts": { - "MAINTAINABILITY": "MEDIUM" + "MAINTAINABILITY": "LOW" }, "attribute": "COMPLETE" }, diff --git a/rules/S6668/metadata.json b/rules/S6668/metadata.json index 2d9815b65a4..bfe7812cc17 100644 --- a/rules/S6668/metadata.json +++ b/rules/S6668/metadata.json @@ -3,7 +3,7 @@ "type": "CODE_SMELL", "code": { "impacts": { - "RELIABILITY": "MEDIUM" + "RELIABILITY": "LOW" }, "attribute": "LOGICAL" }, diff --git a/rules/S6669/metadata.json b/rules/S6669/metadata.json index 7aafe7cfa83..08986912cc5 100644 --- a/rules/S6669/metadata.json +++ b/rules/S6669/metadata.json @@ -3,7 +3,7 @@ "type": "CODE_SMELL", "code": { "impacts": { - "MAINTAINABILITY": "MEDIUM" + "MAINTAINABILITY": "LOW" }, "attribute": "CONVENTIONAL" }, diff --git a/rules/S6670/metadata.json b/rules/S6670/metadata.json index 669e4231e40..74f8c5fe9fd 100644 --- a/rules/S6670/metadata.json +++ b/rules/S6670/metadata.json @@ -3,7 +3,7 @@ "type": "CODE_SMELL", "code": { "impacts": { - "RELIABILITY": "MEDIUM" + "RELIABILITY": "LOW" }, "attribute": "LOGICAL" }, diff --git a/rules/S6672/metadata.json b/rules/S6672/metadata.json index ea51aecca44..a5b1cf8f380 100644 --- a/rules/S6672/metadata.json +++ b/rules/S6672/metadata.json @@ -3,7 +3,7 @@ "type": "CODE_SMELL", "code": { "impacts": { - "MAINTAINABILITY": "MEDIUM" + "MAINTAINABILITY": "LOW" }, "attribute": "CONVENTIONAL" }, diff --git a/rules/S6674/metadata.json b/rules/S6674/metadata.json index 0b91e8ddbda..9b89038acad 100644 --- a/rules/S6674/metadata.json +++ b/rules/S6674/metadata.json @@ -3,7 +3,7 @@ "type": "BUG", "code": { "impacts": { - "RELIABILITY": "MEDIUM" + "RELIABILITY": "HIGH" }, "attribute": "LOGICAL" }, diff --git a/rules/S6675/metadata.json b/rules/S6675/metadata.json index 11ef6f65118..b40390efe37 100644 --- a/rules/S6675/metadata.json +++ b/rules/S6675/metadata.json @@ -3,7 +3,7 @@ "type": "CODE_SMELL", "code": { "impacts": { - "MAINTAINABILITY": "MEDIUM" + "MAINTAINABILITY": "LOW" }, "attribute": "LOGICAL" }, diff --git a/rules/S6776/metadata.json b/rules/S6776/metadata.json index 47cd26d2991..b783f0ab937 100644 --- a/rules/S6776/metadata.json +++ b/rules/S6776/metadata.json @@ -8,7 +8,7 @@ }, "tags": [ ], - "defaultSeverity": "Major", + "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-6776", "sqKey": "S6776", "scope": "All", diff --git a/rules/S6798/csharp/metadata.json b/rules/S6798/csharp/metadata.json index 33cd8322fb3..bb3ff81ab60 100644 --- a/rules/S6798/csharp/metadata.json +++ b/rules/S6798/csharp/metadata.json @@ -17,7 +17,7 @@ "quickfix": "infeasible", "code": { "impacts": { - "RELIABILITY": "HIGH" + "RELIABILITY": "MEDIUM" }, "attribute": "LOGICAL" } diff --git a/rules/S6800/csharp/metadata.json b/rules/S6800/csharp/metadata.json index a1ce051a8f4..4ec76736793 100644 --- a/rules/S6800/csharp/metadata.json +++ b/rules/S6800/csharp/metadata.json @@ -17,7 +17,7 @@ "quickfix": "infeasible", "code": { "impacts": { - "RELIABILITY": "HIGH" + "RELIABILITY": "MEDIUM" }, "attribute": "LOGICAL" } diff --git a/rules/S6869/kubernetes/metadata.json b/rules/S6869/kubernetes/metadata.json index 71145c2488f..5c536161f3d 100644 --- a/rules/S6869/kubernetes/metadata.json +++ b/rules/S6869/kubernetes/metadata.json @@ -8,7 +8,7 @@ }, "tags": [ ], - "defaultSeverity": "Major", + "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-6869", "sqKey": "S6869", "scope": "All", diff --git a/rules/S6930/metadata.json b/rules/S6930/metadata.json index 3458d163fde..e19a6f759db 100644 --- a/rules/S6930/metadata.json +++ b/rules/S6930/metadata.json @@ -19,7 +19,7 @@ "quickfix": "targeted", "code": { "impacts": { - "RELIABILITY": "HIGH" + "RELIABILITY": "MEDIUM" }, "attribute": "LOGICAL" } diff --git a/rules/S6931/metadata.json b/rules/S6931/metadata.json index 1eca95cf662..b800792e4a9 100644 --- a/rules/S6931/metadata.json +++ b/rules/S6931/metadata.json @@ -19,7 +19,7 @@ "quickfix": "partial", "code": { "impacts": { - "MAINTAINABILITY": "HIGH" + "MAINTAINABILITY": "MEDIUM" }, "attribute": "CLEAR" } diff --git a/rules/S6932/csharp/metadata.json b/rules/S6932/csharp/metadata.json index f4ccb751593..50190600c4d 100644 --- a/rules/S6932/csharp/metadata.json +++ b/rules/S6932/csharp/metadata.json @@ -17,7 +17,7 @@ "quickfix": "infeasible", "code": { "impacts": { - "MAINTAINABILITY": "HIGH", + "MAINTAINABILITY": "MEDIUM", "RELIABILITY": "MEDIUM", "SECURITY": "MEDIUM" }, diff --git a/rules/S6934/metadata.json b/rules/S6934/metadata.json index da9deaf5562..4fd75895d86 100644 --- a/rules/S6934/metadata.json +++ b/rules/S6934/metadata.json @@ -17,7 +17,7 @@ "quickfix": "partial", "code": { "impacts": { - "MAINTAINABILITY": "HIGH" + "MAINTAINABILITY": "MEDIUM" }, "attribute": "CLEAR" } diff --git a/rules/S6960/metadata.json b/rules/S6960/metadata.json index 1558912db77..72ec04c17c4 100644 --- a/rules/S6960/metadata.json +++ b/rules/S6960/metadata.json @@ -18,7 +18,7 @@ "quickfix": "partial", "code": { "impacts": { - "MAINTAINABILITY": "HIGH" + "MAINTAINABILITY": "MEDIUM" }, "attribute": "MODULAR" } diff --git a/rules/S6964/metadata.json b/rules/S6964/metadata.json index 9f1d7c0dda6..eefe94d99aa 100644 --- a/rules/S6964/metadata.json +++ b/rules/S6964/metadata.json @@ -17,7 +17,7 @@ "quickfix": "targeted", "code": { "impacts": { - "RELIABILITY": "HIGH" + "RELIABILITY": "MEDIUM" }, "attribute": "TRUSTWORTHY" } diff --git a/rules/S6967/metadata.json b/rules/S6967/metadata.json index bc3ac135d2e..0a9f51639c9 100644 --- a/rules/S6967/metadata.json +++ b/rules/S6967/metadata.json @@ -9,7 +9,7 @@ "tags": [ "asp.net" ], - "defaultSeverity": "Major", + "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-6967", "sqKey": "S6967", "scope": "All", diff --git a/rules/S6968/csharp/metadata.json b/rules/S6968/csharp/metadata.json index 6efdda55951..d7f483209a5 100644 --- a/rules/S6968/csharp/metadata.json +++ b/rules/S6968/csharp/metadata.json @@ -17,7 +17,7 @@ "quickfix": "partial", "code": { "impacts": { - "MAINTAINABILITY": "HIGH" + "MAINTAINABILITY": "MEDIUM" }, "attribute": "CLEAR" } diff --git a/rules/S7018/docker/metadata.json b/rules/S7018/docker/metadata.json index 5bc765f9727..f009c1fe577 100644 --- a/rules/S7018/docker/metadata.json +++ b/rules/S7018/docker/metadata.json @@ -16,9 +16,7 @@ "quickfix": "unknown", "code": { "impacts": { - "MAINTAINABILITY": "MEDIUM", - "RELIABILITY": "LOW", - "SECURITY": "LOW" + "MAINTAINABILITY": "LOW" }, "attribute": "CONVENTIONAL" } diff --git a/rules/S7019/docker/metadata.json b/rules/S7019/docker/metadata.json index 1d9e68151b0..a9d6d4a998f 100644 --- a/rules/S7019/docker/metadata.json +++ b/rules/S7019/docker/metadata.json @@ -17,7 +17,7 @@ "code": { "impacts": { "MAINTAINABILITY": "MEDIUM", - "RELIABILITY": "HIGH" + "RELIABILITY": "MEDIUM" }, "attribute": "CONVENTIONAL" } diff --git a/rules/S7019/docker/rule.adoc b/rules/S7019/docker/rule.adoc index 79152084ec6..b58061fac80 100644 --- a/rules/S7019/docker/rule.adoc +++ b/rules/S7019/docker/rule.adoc @@ -1,8 +1,14 @@ -In Dockerfiles, it is recommended to use the exec form for `CMD` and `ENTRYPOINT` instructions. The exec form, which is represented as a JSON array, ensures that the process runs directly without being wrapped in a shell. This allows OS signals like SIGTERM and SIGINT to be received by the process. This practice enhances the reliability and control of your Docker containers. +In Dockerfiles, it is recommended to use the exec form for `CMD` and `ENTRYPOINT` instructions. +The exec form, which is represented as a JSON array, ensures that the process runs directly without being wrapped in a shell. +This allows OS signals like SIGTERM and SIGINT to be received by the process. This practice enhances the reliability and control of your Docker containers. == Why is this an issue? -Using the shell form instead of the exec form for CMD and ENTRYPOINT instructions in Dockerfiles can lead to several issues. When you use the shell form, the executable runs as a child process to a shell, which does not pass OS signals. This can cause problems when trying to gracefully stop containers because the main process will not receive the signal intended to terminate it. Moreover, the exec form provides more control and predictability over the execution of the command. It does not invoke a command shell, which means it does not have the potential side effects of shell processing. +Using the shell form instead of the exec form for CMD and ENTRYPOINT instructions in Dockerfiles can lead to several issues. +When you use the shell form, the executable runs as a child process to a shell, which does not pass OS signals. +This can cause problems when trying to gracefully stop containers because the main process will not receive the signal intended to terminate it. +Moreover, the exec form provides more control and predictability over the execution of the command. +It does not invoke a command shell, which means it does not have the potential side effects of shell processing. == How to fix it diff --git a/rules/S7020/docker/metadata.json b/rules/S7020/docker/metadata.json index 62292bbcb69..8f40622e68d 100644 --- a/rules/S7020/docker/metadata.json +++ b/rules/S7020/docker/metadata.json @@ -8,7 +8,7 @@ }, "tags": [ ], - "defaultSeverity": "Major", + "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-7020", "sqKey": "S7020", "scope": "All", @@ -16,7 +16,7 @@ "quickfix": "unknown", "code": { "impacts": { - "MAINTAINABILITY": "HIGH" + "MAINTAINABILITY": "LOW" }, "attribute": "CONVENTIONAL" } diff --git a/rules/S7021/docker/metadata.json b/rules/S7021/docker/metadata.json index 7c12987c1cb..1fa351169f1 100644 --- a/rules/S7021/docker/metadata.json +++ b/rules/S7021/docker/metadata.json @@ -16,7 +16,7 @@ "quickfix": "unknown", "code": { "impacts": { - "RELIABILITY": "HIGH" + "RELIABILITY": "MEDIUM" }, "attribute": "CONVENTIONAL" } diff --git a/rules/S7023/docker/metadata.json b/rules/S7023/docker/metadata.json index 7b193e8ebaa..a528370c661 100644 --- a/rules/S7023/docker/metadata.json +++ b/rules/S7023/docker/metadata.json @@ -17,7 +17,7 @@ "code": { "impacts": { "MAINTAINABILITY": "MEDIUM", - "RELIABILITY": "HIGH", + "RELIABILITY": "MEDIUM", "SECURITY": "MEDIUM" }, "attribute": "CONVENTIONAL" diff --git a/rules/S7026/docker/metadata.json b/rules/S7026/docker/metadata.json index ad5dcccf5a3..0ec2a048309 100644 --- a/rules/S7026/docker/metadata.json +++ b/rules/S7026/docker/metadata.json @@ -16,7 +16,7 @@ "quickfix": "unknown", "code": { "impacts": { - "MAINTAINABILITY": "MEDIUM" + "MAINTAINABILITY": "LOW" }, "attribute": "CONVENTIONAL" } diff --git a/rules/S7030/docker/metadata.json b/rules/S7030/docker/metadata.json index b98d3cffc47..1d35df87ea8 100644 --- a/rules/S7030/docker/metadata.json +++ b/rules/S7030/docker/metadata.json @@ -8,7 +8,7 @@ }, "tags": [ ], - "defaultSeverity": "Major", + "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-7030", "sqKey": "S7030", "scope": "All", diff --git a/rules/S7031/docker/metadata.json b/rules/S7031/docker/metadata.json index 664fcabba17..d8dc207f3b0 100644 --- a/rules/S7031/docker/metadata.json +++ b/rules/S7031/docker/metadata.json @@ -8,7 +8,7 @@ }, "tags": [ ], - "defaultSeverity": "Major", + "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-7031", "sqKey": "S7031", "scope": "All", @@ -16,7 +16,7 @@ "quickfix": "unknown", "code": { "impacts": { - "MAINTAINABILITY": "HIGH" + "MAINTAINABILITY": "LOW" }, "attribute": "CONVENTIONAL" } diff --git a/rules/S7116/cfamily/metadata.json b/rules/S7116/cfamily/metadata.json new file mode 100644 index 00000000000..6b4a4a58b4c --- /dev/null +++ b/rules/S7116/cfamily/metadata.json @@ -0,0 +1,24 @@ +{ + "title": "The first element of an array should not be accessed implicitly", + "type": "CODE_SMELL", + "status": "ready", + "remediation": { + "func": "Constant\/Issue", + "constantCost": "5min" + }, + "tags": [ + "confusing" + ], + "defaultSeverity": "Minor", + "ruleSpecification": "RSPEC-7116", + "sqKey": "S7116", + "scope": "All", + "defaultQualityProfiles": ["Sonar way"], + "quickfix": "covered", + "code": { + "impacts": { + "MAINTAINABILITY": "LOW" + }, + "attribute": "CLEAR" + } +} diff --git a/rules/S7116/cfamily/rule.adoc b/rules/S7116/cfamily/rule.adoc new file mode 100644 index 00000000000..c5cdecd087e --- /dev/null +++ b/rules/S7116/cfamily/rule.adoc @@ -0,0 +1,152 @@ +The first element of an array should be accessed using the subscript operator (`array[0]`), +instead of dereferencing the array (``++*array++``) or using the arrow operator on it (``++array->mem++``). + +== Why is this an issue? + +When an expression refers to an object of array type, +it can produce a pointer to the first element of the array. +This behavior is known as array-to-pointer decay or simply array decay. + +Because of array-to-pointer decay, it is possible to use the dereference operator (`*`) or +the pointer member access operator (``++->++``) on an array object to access its first element. +However, it remains unclear if accessing the first element was intentional. +In contrast, using array subscript (`[0]`) removes any such ambiguity. + +This rule raises an issue when the deference operator or the member access operator is used +on an array. + +[source,c] +---- +struct Struct { int x; }; +Struct elems[10]; +int integers[5]; + +void init() { + for (int i = 0; i < 5; ++i) { + *integers = i; // Noncompliant: "*" used to access "integers[0]" + } +} + +void process() { + for (int i = 0; i < 10; ++i) { + elems->x *= 2; // Noncompliant: "->" used to access "elems[0]" + } +} +---- + +== How to fix it + +The fix depends on the intended effect of the code: + +* If the intent is to access the first element, ``++->++`` or `*` should be replaced with an explicit subscripts with `[0]`; +* If another element was meant to be accessed, a subscript with the appropriate index should be used; +* If the intent was to dereference a pointer (for instance to iterate on array elements), the array should be replaced with the appropriate pointer. + +=== Code examples + +==== Noncompliant code example + +[source,c,diff-id=1,diff-type=noncompliant] +---- +struct Struct { int x; }; +Struct elems[10]; + +void process() { + for (int i = 0; i < 10; ++i) { + elems->x *= 2; // Noncompliant: "->" used to access "elems[0]" + } +} +---- + +==== Compliant solution + +If the access to the first element was intentional, you can use subscript explicitly: +[source,cpp,,diff-id=1,diff-type=compliant] +---- +struct Struct { int x; }; +Struct elems[10]; + +void process() { + for (int i = 0; i < 10; ++i) { + elems[0].x *= 2; // Compliant: element accessed through subscript + } +} +---- + +Otherwise, if ``++i++``th element was to be updated: +[source,cpp] +---- +struct Struct { int x; }; +Struct elems[10]; + +void process() { + for (int i = 0; i < 10; ++i) { + elems[i].x *= 2; // Compliant: element accessed through subscript + } +} +---- + +Finally, the ``++i++``th element can be accessed using ``++->++`` and `elem` pointer: +[source,cpp] +---- +struct Struct { int x; }; +Struct elems[10]; + +void process() { + for (Struct* elem = elems, end = elems + 10; elem != end; ++elem) { + elem->x *= 2; // Compliant: dereferencing a pointer + } +} +---- + +==== Noncompliant code example + +[source,cpp,diff-id=2,diff-type=noncompliant] +---- +int integers[5]; + +void init() { + for (int i = 0; i < 5; ++i) { + *integers = i; // Noncompliant: "*" used to access "integers[0]" + } +} +---- + +==== Compliant solution + +The intent was probably to access the ``++i++``th element of `integers`: + +[source,cpp,diff-id=2,diff-type=compliant] +---- +int integers[5]; + +void init() { + for (int i = 0; i < 5; ++i) { + integers[i] = i; // Compliant: element accessed through subscript + } +} +---- + +Or, to preserve current behavior, use a subscript to explicitly access the first element: + +[source,cpp] +---- +int integers[5]; + +void init() { + for (int i = 0; i < 5; ++i) { + integers[0] = i; // Compliant: element accessed through subscript + } +} +---- + + +== Resources + +=== Documentation + +* {cpp} reference - https://en.cppreference.com/w/cpp/language/array#Array-to-pointer_decay[Array-to-pointer decay] + +=== Related rules + + * S945 detects array decays that happen when passing an array object to a function that has a pointer parameter. diff --git a/rules/S7116/metadata.json b/rules/S7116/metadata.json new file mode 100644 index 00000000000..2c63c085104 --- /dev/null +++ b/rules/S7116/metadata.json @@ -0,0 +1,2 @@ +{ +} diff --git a/rules/S7132/cfamily/metadata.json b/rules/S7132/cfamily/metadata.json new file mode 100644 index 00000000000..c98a08922c9 --- /dev/null +++ b/rules/S7132/cfamily/metadata.json @@ -0,0 +1,27 @@ +{ + "title": "std::string_view::data() should not be passed to API expecting C-style strings", + "type": "CODE_SMELL", + "status": "ready", + "remediation": { + "func": "Constant\/Issue", + "constantCost": "10min" + }, + "tags": [ + "suspicious" + ], + "defaultSeverity": "Critical", + "ruleSpecification": "RSPEC-7132", + "sqKey": "S7132", + "scope": "All", + "defaultQualityProfiles": [ + "Sonar way" + ], + "quickfix": "partial", + "code": { + "impacts": { + "RELIABILITY": "HIGH", + "SECURITY": "MEDIUM" + }, + "attribute": "LOGICAL" + } +} diff --git a/rules/S7132/cfamily/rule.adoc b/rules/S7132/cfamily/rule.adoc new file mode 100644 index 00000000000..cef9a704aa7 --- /dev/null +++ b/rules/S7132/cfamily/rule.adoc @@ -0,0 +1,136 @@ +== Why is this an issue? + +In C, and to some extend in {cpp}, strings are arrays of characters terminated by a null character that is used as a sentinel denoting the end of the string. Therefore, it is common to pass to a function a pointer to the start of a string and expect it to iterate on the content until it reaches the final null character. + +`std::string_view` takes another approach: It stores a pointer to the start of the string and the length of the string. This allows, in particular, to have a `string_view` that refers to a substring of a string. In such situations, the last character of the `string_view` will not be followed by a null character. + +This is usually not a problem when working with `string_view`, but can become one when a pointer to the start of the string is extracted from a `string_view` by calling `data()`. This pointer will usually not point to a string with a null character at the right place, and if passed to a function that expects a null-terminated string, this function will not be able to determine the end of the `string_view`. + +This kind of situation usually happens when partially modernizing code that used to work with C-style strings or `std::string` to use `std::string_view` instead (S7121 and S7118 detect similar issues linked to partial string modernization). + +[source,cpp] +---- +void v1(char *s) { // Initial, C-style code + doSomething(strlen(s)); +} + +void v2(std:::string const &s) { // Modernized code, not optimal, but correct with minimal changes + doSomething(strlen(s.data())); +} + +void v3(std::string_view s) { // Second modernization step, becomes incorrect + doSomething(strlen(s.data())); // Noncompliant +} +---- + +This rules raises an issue when the result of calling `data()` on a `string_view` (or any specialization of `std::basic_string_view`) is passed to: + +* a one-argument constructor of `std::string`, `std::string_view` (as well as wide or unicode variants of those classes), +* any function from the C standard library that expects a null-terminated string. + + +=== What is the potential impact? + +If the `string_view` refers to a part of a larger string that is itself null-terminated, the function will read past the end of the view, until the end of the underlying string. This could yield stange results, and potentially leak sensitive information. + +[source,cpp] +---- +std::string credentials = getCredentials(); // Expects a string "user:password" +auto user = std::string_view(credentials.c_str(), credentials.find(':')); +// This will print the user name, but will not stop at the end of the user name, +// it will also print the password. +printf("User: %s", user.data()); // Noncompliant +---- + + +The discrepancy between the `size()` function of a `string_view` and the number of characters read by a function considering the string to be null-terminated could also lead to buffer overflows. + +[source,cpp] +---- +char *userBuffer = new char[user.size() + 1]; +strcpy(userBuffer, user.data()); // Noncompliant: buffer overflow +---- + +In the general case, if the `string_view` does not refer to (part of) a null-terminated string, any function that expects a null-terminated string will lead to a buffer overflow. + +== How to fix it + +=== When working with {cpp} types + +When constructing a `std::string` or another `std::string_view` from a `string_view`, you don't have to call `data`, specific constructors already exist for this purpose. + +==== Noncompliant code example + +[source,cpp,diff-id=1,diff-type=noncompliant] +---- +void f(std::string_view sv) { + std::string s(sv.data()); // Noncompliant + std::string_view sv2(sv.data()); // Noncompliant +} +---- + +==== Compliant solution + +[source,cpp,diff-id=1,diff-type=compliant] +---- +void f(std::string_view sv) { + std::string s(sv); // Noncompliant + std::string_view sv2(sv); // Noncompliant +} +---- + +=== When working with the C library + +When calling a function from the C library that expect a C-style string argument, the best is usually to use a replacement for that function that directly works with `std::string_view`. + +==== Noncompliant code example + +[source,cpp,diff-id=2,diff-type=noncompliant] +---- +void f(std::string_view sv1, std::string_view sv2) { + auto l = strlen(sv1.data()); // Noncompliant + auto p = strstr(sv1.data(), sv2.data()); // Noncompliant + char *buffer = new char[sv1.size() + 1]; + strcpy(buffer, sv1.data()); // Noncompliant + printf("Result: %s\n", sv1.data()); // Noncompliant +} +---- + +==== Compliant solution + +[source,cpp,diff-id=2,diff-type=compliant] +---- +void f(std::string_view sv1, std::string_view sv2) { + auto l = sv1.size(); // Compliant + auto i = sv1.find(sv2); // Compliant + char *buffer = new char[sv1.size() + 1]; + std::copy(sv1.begin(), sv1.end(), buffer); + // In C++23, previous versions could use std::format or a stream + std::println("Result: {}", sv1); // Compliant +} +---- + +If there is no direct replacement for a specific call, it is always possible the create a temporary `string` from the `string_view` and pass the result of `c_str()` to the function, at the cost of an extra memory allocation and copy of the characters of the `string_view`. + +[source,cpp] +---- +void f(std::string_view sv1, std::string_view sv2) { + std::string s1 {sv1}; + std::string s2 {sv2}; + auto l = strlen(s1.c_str()); // Noncompliant + auto p = strstr(s1.c_str(), s2.c_str()); // Noncompliant + char *buffer = new char[s1.size() + 1]; + strcpy(buffer, s1.c_str()); // Noncompliant + printf("Result: %s\n", s1.c_str()); // Noncompliant +} +---- + +== Resources + +=== Related rules + +While calling `data()` (and `c_str()`) on a `std::string` is not as dangerous as on a `std::string_view`, because a `std::string` is always null-terminated, other rules focus on possible misuses of these functions: + +* S7121 triggers when the resulting character pointer is immediately converted back to a string, leading to bad performance. +* S7118 proposes direct alternatives to calling a C library function on the internal buffer of the string. + diff --git a/rules/S7132/metadata.json b/rules/S7132/metadata.json new file mode 100644 index 00000000000..2c63c085104 --- /dev/null +++ b/rules/S7132/metadata.json @@ -0,0 +1,2 @@ +{ +} diff --git a/rules/S7144/metadata.json b/rules/S7144/metadata.json new file mode 100644 index 00000000000..2c63c085104 --- /dev/null +++ b/rules/S7144/metadata.json @@ -0,0 +1,2 @@ +{ +} diff --git a/rules/S7144/secrets/metadata.json b/rules/S7144/secrets/metadata.json new file mode 100644 index 00000000000..aad41af302d --- /dev/null +++ b/rules/S7144/secrets/metadata.json @@ -0,0 +1,56 @@ +{ + "title": "BitBucket OAuth credentials should not be disclosed", + "type": "VULNERABILITY", + "code": { + "impacts": { + "SECURITY": "HIGH" + }, + "attribute": "TRUSTWORTHY" + }, + "status": "ready", + "remediation": { + "func": "Constant\/Issue", + "constantCost": "30min" + }, + "tags": [ + "cwe", + "cert" + ], + "defaultSeverity": "Blocker", + "ruleSpecification": "RSPEC-7144", + "sqKey": "S7144", + "scope": "All", + "securityStandards": { + "CWE": [ + 798, + 259 + ], + "OWASP": [ + "A3" + ], + "CERT": [ + "MSC03-J." + ], + "OWASP Top 10 2021": [ + "A7" + ], + "PCI DSS 3.2": [ + "6.5.10" + ], + "PCI DSS 4.0": [ + "6.2.4" + ], + "ASVS 4.0": [ + "2.10.4", + "3.5.2", + "6.4.1" + ], + "STIG ASD_V5R3": [ + "V-222642" + ] + }, + "defaultQualityProfiles": [ + "Sonar way" + ], + "quickfix": "unknown" +} diff --git a/rules/S7144/secrets/rule.adoc b/rules/S7144/secrets/rule.adoc new file mode 100644 index 00000000000..1fb4e62c57d --- /dev/null +++ b/rules/S7144/secrets/rule.adoc @@ -0,0 +1,62 @@ + +include::../../../shared_content/secrets/description.adoc[] + +== Why is this an issue? + +include::../../../shared_content/secrets/rationale.adoc[] + +If attackers gain access to a BitBucket OAuth credentials, they will be able to interact with BitBucket API on behalf of the compromised account. + +=== What is the potential impact? + +Bitbucket keys allow external services to access resources on a user’s behalf with the OAuth 2.0 protocol. +They are used to authenticate applications, services, or APIs using BitBucket for login and access-control. + +Below are some real-world scenarios that illustrate some impacts of an attacker +exploiting the secret. + +include::../../../shared_content/secrets/impact/source_code_compromise.adoc[] + +include::../../../shared_content/secrets/impact/supply_chain_attack.adoc[] + +== How to fix it + +include::../../../shared_content/secrets/fix/revoke.adoc[] + +include::../../../shared_content/secrets/fix/recent_use.adoc[] + +include::../../../shared_content/secrets/fix/vault.adoc[] + +=== Code examples + + +==== Noncompliant code example + +[source,java,diff-id=1,diff-type=noncompliant,subs="attributes"] +---- +props.set("bitbucket.oauth-key", "MP76PZGLQmw63rxZYJ") // Noncompliant +props.set("bitbucket.oauth-secret", "ASgzPac3EPbXHbTSw6DyUagJZ8ThnUFG") // Noncompliant +---- + +==== Compliant solution + +[source,java,diff-id=1,diff-type=compliant,subs="attributes"] +---- +props.set("bitbucket.oauth-key", System.getenv("BITBUCKET_OAUTH_KEY") +props.set("bitbucket.oauth-secret", System.getenv("BITBUCKET_OAUTH_SECRET") +---- + + +//=== How does this work? + +//=== Pitfalls + +//=== Going the extra mile + +== Resources + +* Bitbucket Support - https://support.atlassian.com/bitbucket-cloud/docs/use-oauth-on-bitbucket-cloud/[Use OAuth on Bitbucket Cloud] + +include::../../../shared_content/secrets/resources/standards.adoc[] + +//=== Benchmarks diff --git a/rules/S7146/metadata.json b/rules/S7146/metadata.json new file mode 100644 index 00000000000..2c63c085104 --- /dev/null +++ b/rules/S7146/metadata.json @@ -0,0 +1,2 @@ +{ +} diff --git a/rules/S7146/secrets/metadata.json b/rules/S7146/secrets/metadata.json new file mode 100644 index 00000000000..b8f073816ab --- /dev/null +++ b/rules/S7146/secrets/metadata.json @@ -0,0 +1,56 @@ +{ + "title": "NuGet.org API keys should not be disclosed", + "type": "VULNERABILITY", + "code": { + "impacts": { + "SECURITY": "HIGH" + }, + "attribute": "TRUSTWORTHY" + }, + "status": "ready", + "remediation": { + "func": "Constant\/Issue", + "constantCost": "30min" + }, + "tags": [ + "cwe", + "cert" + ], + "defaultSeverity": "Blocker", + "ruleSpecification": "RSPEC-7146", + "sqKey": "S7146", + "scope": "All", + "securityStandards": { + "CWE": [ + 798, + 259 + ], + "OWASP": [ + "A3" + ], + "CERT": [ + "MSC03-J." + ], + "OWASP Top 10 2021": [ + "A7" + ], + "PCI DSS 3.2": [ + "6.5.10" + ], + "PCI DSS 4.0": [ + "6.2.4" + ], + "ASVS 4.0": [ + "2.10.4", + "3.5.2", + "6.4.1" + ], + "STIG ASD_V5R3": [ + "V-222642" + ] + }, + "defaultQualityProfiles": [ + "Sonar way" + ], + "quickfix": "unknown" +} diff --git a/rules/S7146/secrets/rule.adoc b/rules/S7146/secrets/rule.adoc new file mode 100644 index 00000000000..14cb45b6ab6 --- /dev/null +++ b/rules/S7146/secrets/rule.adoc @@ -0,0 +1,43 @@ + +include::../../../shared_content/secrets/description.adoc[] + +== Why is this an issue? + +include::../../../shared_content/secrets/rationale.adoc[] + +If an attacker gains access to a NuGet.org API key, they might be able to gain access to any private package linked to this token. + +=== What is the potential impact? + +The exact impact of the compromise of an NuGet.org API key varies depending on the permissions granted to this token. It can range from loss of sensitive data and source code to severe supply chain attacks. + +include::../../../shared_content/secrets/impact/source_code_compromise.adoc[] + +include::../../../shared_content/secrets/impact/supply_chain_attack.adoc[] + +== How to fix it + +include::../../../shared_content/secrets/fix/revoke.adoc[] + +include::../../../shared_content/secrets/fix/vault.adoc[] + +=== Code examples + +:example_secret: oy2brfuhn45jupm5idtgjbmdlmmpisdgrl52g4koahuq2y +:example_name: nuget.api-key +:example_env: NUGET_API_KEY + +include::../../../shared_content/secrets/examples.adoc[] + +=== Going the extra mile + +include::../../../shared_content/secrets/extra_mile/permissions_scope.adoc[] + +== Resources + +=== Documentation + +Microsoft Learn - https://learn.microsoft.com/en-us/nuget/nuget-org/scoped-api-keys[NuGet.org Scoped API keys] + +include::../../../shared_content/secrets/resources/standards.adoc[] + diff --git a/rules/S7148/metadata.json b/rules/S7148/metadata.json new file mode 100644 index 00000000000..2c63c085104 --- /dev/null +++ b/rules/S7148/metadata.json @@ -0,0 +1,2 @@ +{ +} diff --git a/rules/S7148/secrets/metadata.json b/rules/S7148/secrets/metadata.json new file mode 100644 index 00000000000..3e3725f2469 --- /dev/null +++ b/rules/S7148/secrets/metadata.json @@ -0,0 +1,56 @@ +{ + "title": "Mailchimp API keys should not be disclosed", + "type": "VULNERABILITY", + "code": { + "impacts": { + "SECURITY": "HIGH" + }, + "attribute": "TRUSTWORTHY" + }, + "status": "ready", + "remediation": { + "func": "Constant\/Issue", + "constantCost": "30min" + }, + "tags": [ + "cwe", + "cert" + ], + "defaultSeverity": "Blocker", + "ruleSpecification": "RSPEC-7148", + "sqKey": "S7148", + "scope": "All", + "securityStandards": { + "CWE": [ + 798, + 259 + ], + "OWASP": [ + "A3" + ], + "CERT": [ + "MSC03-J." + ], + "OWASP Top 10 2021": [ + "A7" + ], + "PCI DSS 3.2": [ + "6.5.10" + ], + "PCI DSS 4.0": [ + "6.2.4" + ], + "ASVS 4.0": [ + "2.10.4", + "3.5.2", + "6.4.1" + ], + "STIG ASD_V5R3": [ + "V-222642" + ] + }, + "defaultQualityProfiles": [ + "Sonar way" + ], + "quickfix": "unknown" +} diff --git a/rules/S7148/secrets/rule.adoc b/rules/S7148/secrets/rule.adoc new file mode 100644 index 00000000000..3583f1b936f --- /dev/null +++ b/rules/S7148/secrets/rule.adoc @@ -0,0 +1,35 @@ + +include::../../../shared_content/secrets/description.adoc[] + +== Why is this an issue? + +include::../../../shared_content/secrets/rationale.adoc[] + +=== What is the potential impact? + +Below are some real-world scenarios that illustrate some impacts of an attacker +exploiting the secret. + +:secret_type: API key + +include::../../../shared_content/secrets/impact/phishing.adoc[] + +include::../../../shared_content/secrets/impact/financial_loss.adoc[] + +== How to fix it + +include::../../../shared_content/secrets/fix/revoke.adoc[] + +include::../../../shared_content/secrets/fix/vault.adoc[] + +=== Code examples + +:example_secret: c16e8cb733ddd860d2d8d8a9bdd00c44-us10 +:example_name: mailchimp-api-key +:example_env: MAILCHIMP_API_KEY + +include::../../../shared_content/secrets/examples.adoc[] + +== Resources + +include::../../../shared_content/secrets/resources/standards.adoc[] diff --git a/rules/S7149/metadata.json b/rules/S7149/metadata.json new file mode 100644 index 00000000000..2c63c085104 --- /dev/null +++ b/rules/S7149/metadata.json @@ -0,0 +1,2 @@ +{ +} diff --git a/rules/S7149/secrets/metadata.json b/rules/S7149/secrets/metadata.json new file mode 100644 index 00000000000..ce101068f6e --- /dev/null +++ b/rules/S7149/secrets/metadata.json @@ -0,0 +1,56 @@ +{ + "title": "Doppler auth tokens should not be disclosed", + "type": "VULNERABILITY", + "code": { + "impacts": { + "SECURITY": "HIGH" + }, + "attribute": "TRUSTWORTHY" + }, + "status": "ready", + "remediation": { + "func": "Constant\/Issue", + "constantCost": "30min" + }, + "tags": [ + "cwe", + "cert" + ], + "defaultSeverity": "Blocker", + "ruleSpecification": "RSPEC-7149", + "sqKey": "S7149", + "scope": "All", + "securityStandards": { + "CWE": [ + 798, + 259 + ], + "OWASP": [ + "A3" + ], + "CERT": [ + "MSC03-J." + ], + "OWASP Top 10 2021": [ + "A7" + ], + "PCI DSS 3.2": [ + "6.5.10" + ], + "PCI DSS 4.0": [ + "6.2.4" + ], + "ASVS 4.0": [ + "2.10.4", + "3.5.2", + "6.4.1" + ], + "STIG ASD_V5R3": [ + "V-222642" + ] + }, + "defaultQualityProfiles": [ + "Sonar way" + ], + "quickfix": "unknown" +} diff --git a/rules/S7149/secrets/rule.adoc b/rules/S7149/secrets/rule.adoc new file mode 100644 index 00000000000..bab910e1fb5 --- /dev/null +++ b/rules/S7149/secrets/rule.adoc @@ -0,0 +1,34 @@ + +include::../../../shared_content/secrets/description.adoc[] + +== Why is this an issue? + +include::../../../shared_content/secrets/rationale.adoc[] + +=== What is the potential impact? + +Below are some real-world scenarios that illustrate some impacts of an attacker +exploiting the secret. + +include::../../../shared_content/secrets/impact/data_compromise.adoc[] + +include::../../../shared_content/secrets/impact/financial_loss.adoc[] + +== How to fix it + +include::../../../shared_content/secrets/fix/revoke.adoc[] + +include::../../../shared_content/secrets/fix/vault.adoc[] + +=== Code examples + +:example_secret: dp.ct.bAqhcVzrhy5cRHkOlNTc0Ve6w5NUDCpcutm8vGE9myi +:example_name: doppler-auth-token +:example_env: DOPPLER_AUTH_TOKEN + +include::../../../shared_content/secrets/examples.adoc[] + +== Resources + +include::../../../shared_content/secrets/resources/standards.adoc[] + diff --git a/shared_content/secrets/impact/phishing.adoc b/shared_content/secrets/impact/phishing.adoc index e3cab8b0067..50e49792572 100644 --- a/shared_content/secrets/impact/phishing.adoc +++ b/shared_content/secrets/impact/phishing.adoc @@ -5,7 +5,7 @@ a malicious domain controlled by the attacker. Spam can cause users to be exposed to the following: -* Unsolicited, inappropriate content, such as pornographic material +* Unsolicited, inappropriate content * Fraudulent attempts to trick users into sending information or money * Abusive or hateful statements * False advertising or fraudulent claims From 24f8487ba0491ef326b32f0a138717e12d55b19a Mon Sep 17 00:00:00 2001 From: Pierre-Loup Date: Wed, 13 Nov 2024 09:52:55 +0100 Subject: [PATCH 4/4] Fix asciidoc --- rules/S7147/secrets/rule.adoc | 1 + 1 file changed, 1 insertion(+) diff --git a/rules/S7147/secrets/rule.adoc b/rules/S7147/secrets/rule.adoc index fb66fda9ed1..bf900b2c141 100644 --- a/rules/S7147/secrets/rule.adoc +++ b/rules/S7147/secrets/rule.adoc @@ -37,6 +37,7 @@ include::../../../shared_content/secrets/examples.adoc[] === Going the extra mile include::../../../shared_content/secrets/extra_mile/permissions_scope.adoc[] + == Resources === Documentation