mirror of
https://github.com/elastic/kibana.git
synced 2025-06-27 10:40:07 -04:00
[9.0] [Security solution][AI assistant] bug: encode security labs content to prevent antivirus false positive (#221656) (#222951)
# Backport This will backport the following commits from `main` to `9.0`: - [[Security solution][AI assistant] bug: encode security labs content to prevent antivirus false positive (#221656)](https://github.com/elastic/kibana/pull/221656) <!--- Backport version: 10.0.0 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sorenlouv/backport) <!--BACKPORT [{"author":{"name":"Kenneth Kreindler","email":"42113355+KDKHD@users.noreply.github.com"},"sourceCommit":{"committedDate":"2025-06-06T09:16:48Z","message":"[Security solution][AI assistant] bug: encode security labs content to prevent antivirus false positive (#221656)\n\n## Summary\n\nFixes: https://github.com/elastic/kibana/issues/202114\n\nSummarize your PR. If it involves visual changes include a screenshot or\ngif.\n\nThis PR addresses the following\n[issue](https://github.com/elastic/kibana/issues/202114).\n\n#### Problem \nThe security labs' content is triggering false positive anti-virus\nalerts from [ESET cyber\nsecurity](https://www.eset.com/uk/home/cyber-security/?srsltid=AfmBOorLSTn6FfQXm9h4rm2nDpma91Q9-IfHmeUg4TuRL4TvMF9xB-Cc).\nThis is because the content contains specific words and YARA rules that\nthe antivirus picks up as malware. The content is not dangerous.\n\n#### Solution \nEncrypt the content so that the Yara rules do not trigger alerts. The\ncontent is encrypted with AES-256-ECB and the key `ELASTIC`. The\nencryption is not secure and does not need to be secure (we just want to\nobfuscate the content).\n\n#### Considerations\n- An alternative approach to fixing this issue that was considered was\nbuilding an integration so that the content is only imported after\nKibana has been started. As a team, we decided against this because it\nis convenient for airgapped systems to have the secure labs content\nshipped with the installation.\n- It would be great to test this fix against many antivirus providers,\nhowever, I haven't found a tool that lets me do this quickly.\n\n## How to test\n#### Verify the security labs content is encoded\n- Check out the PR\n- Run the following\n```bash\nyarn kbn bootstrap\nyarn build\n```\n- The build may not fully complete locally; however, you should still be\nable to see the build files at `/build`. Inspect the content of the\nfollowing folder:\n`build/kibana/node_modules/@kbn/elastic-assistant-plugin/server/knowledge_base/security_labs`\nand verify that only files ending with `.encoded.md` exist. The content\nof these files should not be human-readable.\n\n\n\n\n#### Verify the security labs content is encoded in the cloud/serverless\nbuild (optional)\n- Log into docker.elastic.co in your terminal. Do this by heading to\nhttps://docker-auth.elastic.co/ (more info\n[here](https://elasticprod.service-now.com/esc?id=kb_article&table=kb_knowledge&sys_id=e8d361c747abb910ffad4438946d439a&recordUrl=kb_view.do%3Fsysparm_article%3DKB0012946))\n- Use the latest CI run to get the serverless docker image and start an\ninteractive shell with it:\n```bash\ndocker run -it --rm docker.elastic.co/kibana-ci/kibana-serverless:pr-221656-ae41a481bbfc sh\n```\n- Inspect the contents of the following directory and verify that only\n`.encoded.md` files exist.\n```bash\ncd node_modules/@kbn/elastic-assistant-plugin/server/knowledge_base/security_labs/\nls\n```\n- Repeat the last 2 steps for the cloud deployment image (also found in\nthe CI)\n\n<img width=\"1233\" alt=\"image\"\nsrc=\"https://github.com/user-attachments/assets/5d308537-b0a9-4bd0-a449-6ddae1ca1bfb\"\n/>\n\n#### Verify security labs content can be installed\n- Start Kibana locally\n- Head over to\n`http://localhost:5601/app/management/kibana/securityAiAssistantManagement?tab=knowledge_base`\nand install the knowledge base.\n\n<img width=\"1454\" alt=\"image\"\nsrc=\"https://github.com/user-attachments/assets/e8c4b557-ea45-4c58-96ff-aacce47e9982\"\n/>\n\n- Check that the security labs content is being installed\n\n<img width=\"1456\" alt=\"image\"\nsrc=\"https://github.com/user-attachments/assets/e7c2b128-ee94-436e-b4e0-1d48cb9d19cb\"\n/>\n\n- Once the security labs content is installed, go to the security AI\nassistant and ask the following: `Which malware is mentioned in the\nsecurity labs content. Include citations`\n- Verify the assistant gives a proper response and the citation links to\nthe Elastic Security Labs page.\n\n<img width=\"859\" alt=\"image\"\nsrc=\"https://github.com/user-attachments/assets/e89e523c-3054-4b8b-a3c3-2857cb1ed8cd\"\n/>\n\n#### Verify antivirus does not trigger (optional)\n- Download and install ESET Cyber security trial from\n[here](https://www.eset.com/uk/home/cyber-security/?srsltid=AfmBOorLSTn6FfQXm9h4rm2nDpma91Q9-IfHmeUg4TuRL4TvMF9xB-Cc)\n- Open up the ESET\n- Click on `custom scan` and run the scan on the folder `build/kibana`\n\n\n\n\n- Expect no alerts to be triggered\n\n\n\n\n\n### Checklist\n\nCheck the PR satisfies following conditions. \n\nReviewers should verify this PR satisfies this list as well.\n\n- [X] Any text added follows [EUI's writing\nguidelines](https://elastic.github.io/eui/#/guidelines/writing), uses\nsentence case text and includes [i18n\nsupport](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md)\n- [X]\n[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)\nwas added for features that require explanation or tutorials\n- [X] [Unit or functional\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\nwere updated or added to match the most common scenarios\n- [X] If a plugin configuration key changed, check if it needs to be\nallowlisted in the cloud and added to the [docker\nlist](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker)\n- [X] This was checked for breaking HTTP API changes, and any breaking\nchanges have been approved by the breaking-change committee. The\n`release_note:breaking` label should be applied in these situations.\n- [X] [Flaky Test\nRunner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was\nused on any tests changed\n- [X] The PR description includes the appropriate Release Notes section,\nand the correct `release_note:*` label is applied per the\n[guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)\n\n### Identify risks\n\nDoes this PR introduce any risks? For example, consider risks like hard\nto test bugs, performance regression, potential of data loss.\n\nDescribe the risk, its severity, and mitigation for each identified\nrisk. Invite stakeholders and evaluate how to proceed before merging.\n\n- [ ] [See some risk\nexamples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx)\n- [ ] ...\n\n---------\n\nCo-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>\nCo-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>\nCo-authored-by: Garrett Spong <garrett.spong@elastic.co>\nCo-authored-by: Garrett Spong <spong@users.noreply.github.com>","sha":"973c8f30a69fa785a149cc50747d99c1db4ce7b6","branchLabelMapping":{"^v9.1.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","ci:cloud-deploy","ci:project-deploy-security","Team:Security Generative AI","backport:version","v9.1.0","v9.0.3","v8.18.3","v8.17.8"],"title":"[Security solution][AI assistant] bug: encode security labs content to prevent antivirus false positive","number":221656,"url":"https://github.com/elastic/kibana/pull/221656","mergeCommit":{"message":"[Security solution][AI assistant] bug: encode security labs content to prevent antivirus false positive (#221656)\n\n## Summary\n\nFixes: https://github.com/elastic/kibana/issues/202114\n\nSummarize your PR. If it involves visual changes include a screenshot or\ngif.\n\nThis PR addresses the following\n[issue](https://github.com/elastic/kibana/issues/202114).\n\n#### Problem \nThe security labs' content is triggering false positive anti-virus\nalerts from [ESET cyber\nsecurity](https://www.eset.com/uk/home/cyber-security/?srsltid=AfmBOorLSTn6FfQXm9h4rm2nDpma91Q9-IfHmeUg4TuRL4TvMF9xB-Cc).\nThis is because the content contains specific words and YARA rules that\nthe antivirus picks up as malware. The content is not dangerous.\n\n#### Solution \nEncrypt the content so that the Yara rules do not trigger alerts. The\ncontent is encrypted with AES-256-ECB and the key `ELASTIC`. The\nencryption is not secure and does not need to be secure (we just want to\nobfuscate the content).\n\n#### Considerations\n- An alternative approach to fixing this issue that was considered was\nbuilding an integration so that the content is only imported after\nKibana has been started. As a team, we decided against this because it\nis convenient for airgapped systems to have the secure labs content\nshipped with the installation.\n- It would be great to test this fix against many antivirus providers,\nhowever, I haven't found a tool that lets me do this quickly.\n\n## How to test\n#### Verify the security labs content is encoded\n- Check out the PR\n- Run the following\n```bash\nyarn kbn bootstrap\nyarn build\n```\n- The build may not fully complete locally; however, you should still be\nable to see the build files at `/build`. Inspect the content of the\nfollowing folder:\n`build/kibana/node_modules/@kbn/elastic-assistant-plugin/server/knowledge_base/security_labs`\nand verify that only files ending with `.encoded.md` exist. The content\nof these files should not be human-readable.\n\n\n\n\n#### Verify the security labs content is encoded in the cloud/serverless\nbuild (optional)\n- Log into docker.elastic.co in your terminal. Do this by heading to\nhttps://docker-auth.elastic.co/ (more info\n[here](https://elasticprod.service-now.com/esc?id=kb_article&table=kb_knowledge&sys_id=e8d361c747abb910ffad4438946d439a&recordUrl=kb_view.do%3Fsysparm_article%3DKB0012946))\n- Use the latest CI run to get the serverless docker image and start an\ninteractive shell with it:\n```bash\ndocker run -it --rm docker.elastic.co/kibana-ci/kibana-serverless:pr-221656-ae41a481bbfc sh\n```\n- Inspect the contents of the following directory and verify that only\n`.encoded.md` files exist.\n```bash\ncd node_modules/@kbn/elastic-assistant-plugin/server/knowledge_base/security_labs/\nls\n```\n- Repeat the last 2 steps for the cloud deployment image (also found in\nthe CI)\n\n<img width=\"1233\" alt=\"image\"\nsrc=\"https://github.com/user-attachments/assets/5d308537-b0a9-4bd0-a449-6ddae1ca1bfb\"\n/>\n\n#### Verify security labs content can be installed\n- Start Kibana locally\n- Head over to\n`http://localhost:5601/app/management/kibana/securityAiAssistantManagement?tab=knowledge_base`\nand install the knowledge base.\n\n<img width=\"1454\" alt=\"image\"\nsrc=\"https://github.com/user-attachments/assets/e8c4b557-ea45-4c58-96ff-aacce47e9982\"\n/>\n\n- Check that the security labs content is being installed\n\n<img width=\"1456\" alt=\"image\"\nsrc=\"https://github.com/user-attachments/assets/e7c2b128-ee94-436e-b4e0-1d48cb9d19cb\"\n/>\n\n- Once the security labs content is installed, go to the security AI\nassistant and ask the following: `Which malware is mentioned in the\nsecurity labs content. Include citations`\n- Verify the assistant gives a proper response and the citation links to\nthe Elastic Security Labs page.\n\n<img width=\"859\" alt=\"image\"\nsrc=\"https://github.com/user-attachments/assets/e89e523c-3054-4b8b-a3c3-2857cb1ed8cd\"\n/>\n\n#### Verify antivirus does not trigger (optional)\n- Download and install ESET Cyber security trial from\n[here](https://www.eset.com/uk/home/cyber-security/?srsltid=AfmBOorLSTn6FfQXm9h4rm2nDpma91Q9-IfHmeUg4TuRL4TvMF9xB-Cc)\n- Open up the ESET\n- Click on `custom scan` and run the scan on the folder `build/kibana`\n\n\n\n\n- Expect no alerts to be triggered\n\n\n\n\n\n### Checklist\n\nCheck the PR satisfies following conditions. \n\nReviewers should verify this PR satisfies this list as well.\n\n- [X] Any text added follows [EUI's writing\nguidelines](https://elastic.github.io/eui/#/guidelines/writing), uses\nsentence case text and includes [i18n\nsupport](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md)\n- [X]\n[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)\nwas added for features that require explanation or tutorials\n- [X] [Unit or functional\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\nwere updated or added to match the most common scenarios\n- [X] If a plugin configuration key changed, check if it needs to be\nallowlisted in the cloud and added to the [docker\nlist](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker)\n- [X] This was checked for breaking HTTP API changes, and any breaking\nchanges have been approved by the breaking-change committee. The\n`release_note:breaking` label should be applied in these situations.\n- [X] [Flaky Test\nRunner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was\nused on any tests changed\n- [X] The PR description includes the appropriate Release Notes section,\nand the correct `release_note:*` label is applied per the\n[guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)\n\n### Identify risks\n\nDoes this PR introduce any risks? For example, consider risks like hard\nto test bugs, performance regression, potential of data loss.\n\nDescribe the risk, its severity, and mitigation for each identified\nrisk. Invite stakeholders and evaluate how to proceed before merging.\n\n- [ ] [See some risk\nexamples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx)\n- [ ] ...\n\n---------\n\nCo-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>\nCo-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>\nCo-authored-by: Garrett Spong <garrett.spong@elastic.co>\nCo-authored-by: Garrett Spong <spong@users.noreply.github.com>","sha":"973c8f30a69fa785a149cc50747d99c1db4ce7b6"}},"sourceBranch":"main","suggestedTargetBranches":["9.0","8.18","8.17"],"targetPullRequestStates":[{"branch":"main","label":"v9.1.0","branchLabelMappingKey":"^v9.1.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/221656","number":221656,"mergeCommit":{"message":"[Security solution][AI assistant] bug: encode security labs content to prevent antivirus false positive (#221656)\n\n## Summary\n\nFixes: https://github.com/elastic/kibana/issues/202114\n\nSummarize your PR. If it involves visual changes include a screenshot or\ngif.\n\nThis PR addresses the following\n[issue](https://github.com/elastic/kibana/issues/202114).\n\n#### Problem \nThe security labs' content is triggering false positive anti-virus\nalerts from [ESET cyber\nsecurity](https://www.eset.com/uk/home/cyber-security/?srsltid=AfmBOorLSTn6FfQXm9h4rm2nDpma91Q9-IfHmeUg4TuRL4TvMF9xB-Cc).\nThis is because the content contains specific words and YARA rules that\nthe antivirus picks up as malware. The content is not dangerous.\n\n#### Solution \nEncrypt the content so that the Yara rules do not trigger alerts. The\ncontent is encrypted with AES-256-ECB and the key `ELASTIC`. The\nencryption is not secure and does not need to be secure (we just want to\nobfuscate the content).\n\n#### Considerations\n- An alternative approach to fixing this issue that was considered was\nbuilding an integration so that the content is only imported after\nKibana has been started. As a team, we decided against this because it\nis convenient for airgapped systems to have the secure labs content\nshipped with the installation.\n- It would be great to test this fix against many antivirus providers,\nhowever, I haven't found a tool that lets me do this quickly.\n\n## How to test\n#### Verify the security labs content is encoded\n- Check out the PR\n- Run the following\n```bash\nyarn kbn bootstrap\nyarn build\n```\n- The build may not fully complete locally; however, you should still be\nable to see the build files at `/build`. Inspect the content of the\nfollowing folder:\n`build/kibana/node_modules/@kbn/elastic-assistant-plugin/server/knowledge_base/security_labs`\nand verify that only files ending with `.encoded.md` exist. The content\nof these files should not be human-readable.\n\n\n\n\n#### Verify the security labs content is encoded in the cloud/serverless\nbuild (optional)\n- Log into docker.elastic.co in your terminal. Do this by heading to\nhttps://docker-auth.elastic.co/ (more info\n[here](https://elasticprod.service-now.com/esc?id=kb_article&table=kb_knowledge&sys_id=e8d361c747abb910ffad4438946d439a&recordUrl=kb_view.do%3Fsysparm_article%3DKB0012946))\n- Use the latest CI run to get the serverless docker image and start an\ninteractive shell with it:\n```bash\ndocker run -it --rm docker.elastic.co/kibana-ci/kibana-serverless:pr-221656-ae41a481bbfc sh\n```\n- Inspect the contents of the following directory and verify that only\n`.encoded.md` files exist.\n```bash\ncd node_modules/@kbn/elastic-assistant-plugin/server/knowledge_base/security_labs/\nls\n```\n- Repeat the last 2 steps for the cloud deployment image (also found in\nthe CI)\n\n<img width=\"1233\" alt=\"image\"\nsrc=\"https://github.com/user-attachments/assets/5d308537-b0a9-4bd0-a449-6ddae1ca1bfb\"\n/>\n\n#### Verify security labs content can be installed\n- Start Kibana locally\n- Head over to\n`http://localhost:5601/app/management/kibana/securityAiAssistantManagement?tab=knowledge_base`\nand install the knowledge base.\n\n<img width=\"1454\" alt=\"image\"\nsrc=\"https://github.com/user-attachments/assets/e8c4b557-ea45-4c58-96ff-aacce47e9982\"\n/>\n\n- Check that the security labs content is being installed\n\n<img width=\"1456\" alt=\"image\"\nsrc=\"https://github.com/user-attachments/assets/e7c2b128-ee94-436e-b4e0-1d48cb9d19cb\"\n/>\n\n- Once the security labs content is installed, go to the security AI\nassistant and ask the following: `Which malware is mentioned in the\nsecurity labs content. Include citations`\n- Verify the assistant gives a proper response and the citation links to\nthe Elastic Security Labs page.\n\n<img width=\"859\" alt=\"image\"\nsrc=\"https://github.com/user-attachments/assets/e89e523c-3054-4b8b-a3c3-2857cb1ed8cd\"\n/>\n\n#### Verify antivirus does not trigger (optional)\n- Download and install ESET Cyber security trial from\n[here](https://www.eset.com/uk/home/cyber-security/?srsltid=AfmBOorLSTn6FfQXm9h4rm2nDpma91Q9-IfHmeUg4TuRL4TvMF9xB-Cc)\n- Open up the ESET\n- Click on `custom scan` and run the scan on the folder `build/kibana`\n\n\n\n\n- Expect no alerts to be triggered\n\n\n\n\n\n### Checklist\n\nCheck the PR satisfies following conditions. \n\nReviewers should verify this PR satisfies this list as well.\n\n- [X] Any text added follows [EUI's writing\nguidelines](https://elastic.github.io/eui/#/guidelines/writing), uses\nsentence case text and includes [i18n\nsupport](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md)\n- [X]\n[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)\nwas added for features that require explanation or tutorials\n- [X] [Unit or functional\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\nwere updated or added to match the most common scenarios\n- [X] If a plugin configuration key changed, check if it needs to be\nallowlisted in the cloud and added to the [docker\nlist](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker)\n- [X] This was checked for breaking HTTP API changes, and any breaking\nchanges have been approved by the breaking-change committee. The\n`release_note:breaking` label should be applied in these situations.\n- [X] [Flaky Test\nRunner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was\nused on any tests changed\n- [X] The PR description includes the appropriate Release Notes section,\nand the correct `release_note:*` label is applied per the\n[guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)\n\n### Identify risks\n\nDoes this PR introduce any risks? For example, consider risks like hard\nto test bugs, performance regression, potential of data loss.\n\nDescribe the risk, its severity, and mitigation for each identified\nrisk. Invite stakeholders and evaluate how to proceed before merging.\n\n- [ ] [See some risk\nexamples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx)\n- [ ] ...\n\n---------\n\nCo-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>\nCo-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>\nCo-authored-by: Garrett Spong <garrett.spong@elastic.co>\nCo-authored-by: Garrett Spong <spong@users.noreply.github.com>","sha":"973c8f30a69fa785a149cc50747d99c1db4ce7b6"}},{"branch":"9.0","label":"v9.0.3","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"8.18","label":"v8.18.3","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"8.17","label":"v8.17.8","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"}]}] BACKPORT--> --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com> Co-authored-by: Garrett Spong <garrett.spong@elastic.co> Co-authored-by: Garrett Spong <spong@users.noreply.github.com>
This commit is contained in:
parent
865fa67b21
commit
d5fd19058f
191 changed files with 670 additions and 7 deletions
1
.github/CODEOWNERS
vendored
1
.github/CODEOWNERS
vendored
|
@ -974,6 +974,7 @@ x-pack/solutions/search/plugins/search_playground @elastic/search-kibana
|
|||
x-pack/solutions/search/plugins/search_solution/search_navigation @elastic/search-kibana
|
||||
x-pack/solutions/search/plugins/search_synonyms @elastic/search-kibana
|
||||
x-pack/solutions/search/plugins/serverless_search @elastic/search-kibana
|
||||
x-pack/solutions/security/packages/ai-security-labs-content @elastic/security-generative-ai
|
||||
x-pack/solutions/security/packages/connectors @elastic/security-threat-hunting-explore
|
||||
x-pack/solutions/security/packages/data-stream-adapter @elastic/security-threat-hunting
|
||||
x-pack/solutions/security/packages/data-table @elastic/security-threat-hunting-investigations
|
||||
|
|
|
@ -170,6 +170,7 @@
|
|||
"@kbn/ai-assistant-cta": "link:x-pack/platform/packages/shared/ai-assistant/ai-assistant-cta",
|
||||
"@kbn/ai-assistant-icon": "link:x-pack/platform/packages/shared/ai-assistant/icon",
|
||||
"@kbn/ai-assistant-management-plugin": "link:src/platform/plugins/shared/ai_assistant_management/selection",
|
||||
"@kbn/ai-security-labs-content": "link:x-pack/solutions/security/packages/ai-security-labs-content",
|
||||
"@kbn/aiops-change-point-detection": "link:x-pack/platform/packages/private/ml/aiops_change_point_detection",
|
||||
"@kbn/aiops-common": "link:x-pack/platform/packages/shared/ml/aiops_common",
|
||||
"@kbn/aiops-components": "link:x-pack/platform/packages/private/ml/aiops_components",
|
||||
|
|
|
@ -24,6 +24,8 @@
|
|||
"@kbn/ai-assistant-icon/*": ["x-pack/platform/packages/shared/ai-assistant/icon/*"],
|
||||
"@kbn/ai-assistant-management-plugin": ["src/platform/plugins/shared/ai_assistant_management/selection"],
|
||||
"@kbn/ai-assistant-management-plugin/*": ["src/platform/plugins/shared/ai_assistant_management/selection/*"],
|
||||
"@kbn/ai-security-labs-content": ["x-pack/solutions/security/packages/ai-security-labs-content"],
|
||||
"@kbn/ai-security-labs-content/*": ["x-pack/solutions/security/packages/ai-security-labs-content/*"],
|
||||
"@kbn/aiops-change-point-detection": ["x-pack/platform/packages/private/ml/aiops_change_point_detection"],
|
||||
"@kbn/aiops-change-point-detection/*": ["x-pack/platform/packages/private/ml/aiops_change_point_detection/*"],
|
||||
"@kbn/aiops-common": ["x-pack/platform/packages/shared/ml/aiops_common"],
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
# @kbn/ai-security-labs-content
|
||||
|
||||
Utility library for AI Security Labs Content.
|
||||
|
||||
### Usages
|
||||
|
||||
#### Encoding Security Labs Content
|
||||
|
||||
This package provides utilities to encode security labs content. Security labs content needs to be encoded in order to prevent the content from triggering false positive security alerts (see this [issue](https://github.com/elastic/kibana/issues/202114)).
|
||||
|
||||
For more information visit the [knowledge base readme](../../../../../x-pack/solutions/security/plugins/elastic_assistant/server/knowledge_base/README.md)
|
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
export { encryptSecurityLabsContent, decryptSecurityLabsContent } from './src/utils';
|
||||
|
||||
/**
|
||||
* Micromatch pattern for plain text markdown files in the security labs content.
|
||||
*/
|
||||
export const PLAIN_TEXT_FILE_MICROMATCH_PATTERN = ['*.md', '!*.encoded.md'];
|
||||
|
||||
/**
|
||||
* Micromatch pattern for encoded markdown files in the security labs content.
|
||||
*/
|
||||
export const ENCODED_FILE_MICROMATCH_PATTERN = ['*.encoded.md'];
|
|
@ -0,0 +1,12 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
preset: '@kbn/test',
|
||||
rootDir: '../../../../..',
|
||||
roots: ['<rootDir>/x-pack/solutions/security/packages/ai-security-labs-content'],
|
||||
};
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"type": "shared-server",
|
||||
"id": "@kbn/ai-security-labs-content",
|
||||
"owner": "@elastic/security-generative-ai",
|
||||
"group": "security",
|
||||
"visibility": "private"
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"name": "@kbn/ai-security-labs-content",
|
||||
"version": "1.0.0",
|
||||
"description": "Utility library for AI security labs content",
|
||||
"license": "Elastic License 2.0",
|
||||
"private": true
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line import/no-nodejs-modules
|
||||
import crypto from 'crypto';
|
||||
|
||||
const key = crypto.createHash('sha256').update('ELASTIC').digest();
|
||||
const keyUint8 = key as unknown as Uint8Array;
|
||||
|
||||
/**
|
||||
* Unsafe encryption function for security labs content.
|
||||
* @param text security labs content to encrypt
|
||||
* @returns Encrypted content as a hex string.
|
||||
*/
|
||||
export function encryptSecurityLabsContent(text: string): string {
|
||||
const cipher = crypto.createCipheriv('aes-256-ecb', keyUint8, null);
|
||||
return cipher.update(text, 'utf8', 'hex') + cipher.final('hex');
|
||||
}
|
||||
|
||||
/**
|
||||
* Decryption function for security labs content.
|
||||
* @param encrypted Encrypted security labs content as a hex string.
|
||||
* @returns Decrypted content as a string.
|
||||
*/
|
||||
export function decryptSecurityLabsContent(encrypted: string): string {
|
||||
const decipher = crypto.createDecipheriv('aes-256-ecb', keyUint8, null);
|
||||
return decipher.update(encrypted, 'hex', 'utf8') + decipher.final('utf8');
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"extends": "../../../../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "target/types",
|
||||
"types": [
|
||||
"jest",
|
||||
"node",
|
||||
]
|
||||
},
|
||||
"include": ["**/*.ts"],
|
||||
"kbn_references": [
|
||||
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*"
|
||||
],
|
||||
}
|
|
@ -27,5 +27,12 @@
|
|||
"spaces",
|
||||
"security"
|
||||
]
|
||||
},
|
||||
"build": {
|
||||
"extraExcludes": [
|
||||
// Only include the encoded versions in the build due to https://github.com/elastic/kibana/issues/202114
|
||||
"**/knowledge_base/security_labs/*.md",
|
||||
"!**/knowledge_base/security_labs/*.encoded.md"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
"license": "Elastic License 2.0",
|
||||
"scripts": {
|
||||
"evaluate-model": "node ./scripts/model_evaluator",
|
||||
"draw-graph": "node ./scripts/draw_graph"
|
||||
"draw-graph": "node ./scripts/draw_graph",
|
||||
"encode-security-labs-content": "node ./scripts/encode_security_labs_content"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
require('../../../../../../src/setup_node_env');
|
||||
require('./encode_security_labs_content_script').encodeSecurityLabsContent();
|
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
/* eslint-disable no-console */
|
||||
|
||||
import * as fs from 'fs/promises';
|
||||
|
||||
import * as path from 'path';
|
||||
import globby from 'globby';
|
||||
import { existsSync } from 'fs';
|
||||
import {
|
||||
ENCODED_FILE_MICROMATCH_PATTERN,
|
||||
PLAIN_TEXT_FILE_MICROMATCH_PATTERN,
|
||||
encryptSecurityLabsContent,
|
||||
} from '@kbn/ai-security-labs-content';
|
||||
|
||||
// Path to the security labs markdown files
|
||||
export const SECURITY_LABS_DIR = path.resolve(__dirname, '../server/knowledge_base/security_labs');
|
||||
|
||||
export const deleteFilesByPattern = async ({
|
||||
directoryPath,
|
||||
pattern,
|
||||
}: {
|
||||
directoryPath: string;
|
||||
pattern: string | string[];
|
||||
}): Promise<void> => {
|
||||
try {
|
||||
console.log(`Deleting files matching pattern "${pattern}" in directory "${directoryPath}"`);
|
||||
const files = await globby(pattern, { cwd: directoryPath });
|
||||
|
||||
if (files.length === 0) {
|
||||
console.log(`No files found matching pattern "${pattern}" in directory "${directoryPath}".`);
|
||||
return;
|
||||
}
|
||||
|
||||
for (const file of files) {
|
||||
const filePath = path.join(directoryPath, file);
|
||||
await fs.unlink(filePath);
|
||||
console.log(`Deleted file: "${filePath}"`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error deleting files: ${error.message}`);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
const encodedFile = async (fileName: string) => {
|
||||
const filePath = path.join(SECURITY_LABS_DIR, fileName);
|
||||
const content = await fs.readFile(filePath, 'utf-8');
|
||||
const encodedContent = encryptSecurityLabsContent(content);
|
||||
const fileNameWithoutExtension = path.basename(fileName, path.extname(fileName));
|
||||
const outputFilePath = path.join(SECURITY_LABS_DIR, `${fileNameWithoutExtension}.encoded.md`);
|
||||
await fs.writeFile(outputFilePath, encodedContent, 'utf-8');
|
||||
console.log(`Encoded file: ${fileName} -> ${outputFilePath}`);
|
||||
return outputFilePath;
|
||||
};
|
||||
|
||||
/**
|
||||
* Main function to encode all markdown files in the security labs directory
|
||||
*/
|
||||
export const encodeSecurityLabsContent = async () => {
|
||||
console.log('Encoding files from:', SECURITY_LABS_DIR);
|
||||
if (!existsSync(SECURITY_LABS_DIR)) {
|
||||
throw new Error(`Input directory does not exist: ${SECURITY_LABS_DIR}`);
|
||||
}
|
||||
await deleteFilesByPattern({
|
||||
directoryPath: SECURITY_LABS_DIR,
|
||||
pattern: ENCODED_FILE_MICROMATCH_PATTERN,
|
||||
});
|
||||
|
||||
const files = await globby(PLAIN_TEXT_FILE_MICROMATCH_PATTERN, {
|
||||
cwd: SECURITY_LABS_DIR,
|
||||
});
|
||||
files.forEach((file) => {
|
||||
encodedFile(file);
|
||||
});
|
||||
};
|
|
@ -12,6 +12,35 @@ The assets are stored in their original source format, so `.asciidoc` for docume
|
|||
|
||||
NOTE: When adding knowledge base assets, please ensure that the source files and directories are not excluded as part of the Kibana build process, otherwise things will work fine locally, but will fail once a distribution has been built (i.e. cloud deployments). See `src/dev/build/tasks/copy_legacy_source_task.ts` for details on exclusion patterns.
|
||||
|
||||
#### Adding new security labs content
|
||||
|
||||
When adding new security labs content ensure that the content follows the same structure as the existing documents. The header of the files must be valid yaml format as described bellow and must be fenced by `---`.
|
||||
|
||||
```
|
||||
---
|
||||
title: <string>
|
||||
slug: <string>
|
||||
date: <ISO 8601 date format>
|
||||
description: <string>
|
||||
author: <list of slugs>
|
||||
image: <string>
|
||||
category: <list of slugs>
|
||||
---
|
||||
|
||||
<content string>
|
||||
```
|
||||
|
||||
The file name should be the article title in snakecase e.g. vulnerability_summary_follina.md
|
||||
|
||||
After adding new articles run the following to create the encoded article:
|
||||
|
||||
```bash
|
||||
cd x-pack/solutions/security/plugins/elastic_assistant
|
||||
yarn encode-security-labs-content
|
||||
```
|
||||
|
||||
Files are encoded due to this [issue](https://github.com/elastic/kibana/issues/202114).
|
||||
|
||||
### Future
|
||||
|
||||
Once asset format and chunking strategies are finalized, we may want to either move the assets to a shared package so they can be consumed by other plugins, or potentially ship the pre-packaged ELSER embeddings as part of a Fleet Integration. For now though, the assets will be included in their source format within the plugin, and can then be processed and embedded at runtime.
|
||||
|
|
|
@ -0,0 +1,144 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import yaml from 'js-yaml';
|
||||
import globby from 'globby';
|
||||
import {
|
||||
decryptSecurityLabsContent,
|
||||
ENCODED_FILE_MICROMATCH_PATTERN,
|
||||
PLAIN_TEXT_FILE_MICROMATCH_PATTERN,
|
||||
} from '@kbn/ai-security-labs-content';
|
||||
|
||||
const isoDateRegex = /^\d{4}-\d{2}-\d{2}$/;
|
||||
|
||||
describe('Security labs content', () => {
|
||||
const directoryPath = path.join(__dirname, 'security_labs');
|
||||
const plainTextFiles: string[] = globby.sync(PLAIN_TEXT_FILE_MICROMATCH_PATTERN, {
|
||||
cwd: directoryPath,
|
||||
});
|
||||
const encodedFiles: string[] = globby.sync(ENCODED_FILE_MICROMATCH_PATTERN, {
|
||||
cwd: directoryPath,
|
||||
});
|
||||
|
||||
describe('plaintext security labs content format', () => {
|
||||
plainTextFiles.forEach((file) => {
|
||||
it(`should have non-empty string content in file: ${file}`, () => {
|
||||
const filePath = path.join(directoryPath, file);
|
||||
const content = fs.readFileSync(filePath, 'utf-8');
|
||||
|
||||
expect(typeof content).toBe('string');
|
||||
expect(content.trim().length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
plainTextFiles.forEach((file) => {
|
||||
it(`should have title and slug defined in yaml header: ${file}`, () => {
|
||||
const filePath = path.join(directoryPath, file);
|
||||
const content = fs.readFileSync(filePath, 'utf-8');
|
||||
|
||||
const split = content.split('---');
|
||||
const yamlString = split[1];
|
||||
const article = split[2];
|
||||
const parsed = yaml.load(yamlString) as {
|
||||
slug: string;
|
||||
title: string;
|
||||
date: string;
|
||||
description: string;
|
||||
author: Array<{ slug: string }>;
|
||||
image: string;
|
||||
category: Array<{ slug: string }>;
|
||||
};
|
||||
|
||||
const slug = parsed.slug;
|
||||
const title = parsed.title;
|
||||
const date = parsed.date;
|
||||
const description = parsed.description;
|
||||
const author = parsed.author;
|
||||
const image = parsed.image;
|
||||
const category = parsed.category;
|
||||
expect(slug).toBeDefined();
|
||||
expect(title).toBeDefined();
|
||||
expect(typeof slug).toBe('string');
|
||||
expect(typeof title).toBe('string');
|
||||
expect(slug.trim().length).toBeGreaterThan(0);
|
||||
expect(title.trim().length).toBeGreaterThan(0);
|
||||
expect(date).toBeDefined();
|
||||
expect(typeof date).toBe('string');
|
||||
expect(date).toMatch(isoDateRegex);
|
||||
expect(description).toBeDefined();
|
||||
expect(author).toBeDefined();
|
||||
expect(Array.isArray(author)).toBe(true);
|
||||
expect(image).toBeDefined();
|
||||
expect(typeof image).toBe('string');
|
||||
expect(image.trim().length).toBeGreaterThan(0);
|
||||
expect(category).toBeDefined();
|
||||
|
||||
expect(article).toBeDefined();
|
||||
expect(typeof article).toBe('string');
|
||||
});
|
||||
});
|
||||
|
||||
plainTextFiles.forEach((file) => {
|
||||
it(`article should come after yaml definition: ${file}`, () => {
|
||||
const filePath = path.join(directoryPath, file);
|
||||
const content = fs.readFileSync(filePath, 'utf-8');
|
||||
|
||||
const split = content.split('---');
|
||||
const article = split[2];
|
||||
|
||||
expect(article).toBeDefined();
|
||||
expect(typeof article).toBe('string');
|
||||
expect(article.trim().length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('encoded security labs content', () => {
|
||||
plainTextFiles.forEach((file) => {
|
||||
it(`corresponding encoded file exists for ${file}`, () => {
|
||||
/**
|
||||
* If this test fails, you probably forgot to run `yarn encode-security-labs-content` in x-pack/solutions/security/plugins/elastic_assistant/package.json
|
||||
*/
|
||||
const encodedFileName = `${path.basename(file, path.extname(file))}.encoded.md`;
|
||||
const encodedFilePath = path.join(directoryPath, encodedFileName);
|
||||
|
||||
// check if encoded file exists
|
||||
expect(encodedFiles).toContain(encodedFileName);
|
||||
expect(fs.existsSync(encodedFilePath)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
encodedFiles.forEach((file) => {
|
||||
it(`corresponding plaintext file exists for ${file}`, () => {
|
||||
const plaintextFileName = `${path.basename(file, path.extname(file))}.md`;
|
||||
const plaintextFilePath = path.join(directoryPath, plaintextFileName);
|
||||
|
||||
// check if plaintext file exists
|
||||
expect(encodedFiles).toContain(plaintextFileName);
|
||||
expect(fs.existsSync(plaintextFilePath)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
plainTextFiles.forEach((file) => {
|
||||
it(`file ${file} was encoded correctly`, () => {
|
||||
const plainTextFilePath = path.join(directoryPath, file);
|
||||
const plainTextContent = fs.readFileSync(plainTextFilePath, 'utf-8');
|
||||
|
||||
const encodedFileName = `${path.basename(file, path.extname(file))}.encoded.md`;
|
||||
const encodedFilePath = path.join(directoryPath, encodedFileName);
|
||||
|
||||
// read encoded content
|
||||
const encodedContent = fs.readFileSync(encodedFilePath, 'utf-8');
|
||||
const decodedContent = decryptSecurityLabsContent(encodedContent);
|
||||
|
||||
expect(decodedContent).toBe(plainTextContent);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1 @@
|
|||
59ffcbaaef80ffbe0e1c9c481b585ca55631d35a955880b5231e7c3bc07e02ae0873d267646a38903e854f9c80c187e0585e46ecd3912c29cfbc6d7925af371cee99ad5ab57bada92802903be5a89ac7109e1d4772260ce06f2a6548a951832c610bcbb26f2084c281f745d15a72b68f3344b357a71493c580ce449f744d4ac56837d65fb24155b2d89c39243ad2d888ff181e0294aebc6e29f0d2f8068d30320c1f1ba56eba545775de13b129b0c375657301249abe7072a22eb407b668bfd9faae339add53612c0e1ba714bac28ee41fed2259273a62ed4265088c9615f4b3bc28c00afd68e7b3d20e2510f6be7b185a82b267d5d818f83b2dc1fcdd81e4b50dd90bd1410d187773b8d614f52596814d73fe08552935423135238a4ffda2eff6cc0c6b6155a604083c0286b8a2d1ff452f98ef4b031c15b44ad66576a0f4f8f781105b3d240227a19b67428410badfdffb530fdf0d1c560a17b70961f037d5c7d1b882ad50897d9078ffdf59321b69174dd5548097a29c84f23a9808fb71b12391a547fd69b5fbfc7badd51be41ba11bfe9f39e911673fd0c6d1eb82d77cb093b5877b41259c6980b1cdf42eabb81452df43a18565a073d69c979ab537c8c5cfb5cc059a239b1f1814982241d2c369603677580c29871d2ff39cfc6f93a3ab43f47667477f325dac18f6cedb3af00944d4304b08802f876b02f0c894abbfcb20f9888e967b8c904bd191ae0f288d490d21830d84ec010bf322e63e7f37036e48aada8f7f15bcc7941cd8bc3ac39bf129cc9634b711822fee50d3cc4fd4355ec59ba21405912685243a657be48366ca1997665bfd21e411f4f04c98be855550b94955e7e3b44df5da856310fbdb2cbf4e6e844cc8e3c6dd5a5b7cbd82a222227b66de87f17003f85e454a47a269a537f420b67cb0c46da3df00b9c7fbd7418da5a9ccfb9b2cf6eb4b6666375211fa17cf67fef251686448c6152e7d9842e622749dd107303a8b6b4b256a73fbf06d8f079f37cbea3dcbb5566c9264fddb3a016bbd2b044a6c29984efc9bcfc8555670174cc8a18dbef11a9e3dc1f9596c6ff878cd391be183d5a184e2ccafcbe7c22f7e07fd43327d7a778e911ce316094dad2de6ac2153cf14efab7475912f3031af5df943ff3397942f7a68f71c8805fec9cbe71bfe8635476b965f4c172aae916e4f1464c7638979e40968e0b9090dce646d2cd85bfb38c6f405dde4c17f4c0a57dc6e50052f4379b7f9a3eeb5000592ab8a6eaf5cc83aa654f403fa2d59ef6057cefcf54e653658f931b8a52dc1b47c3ad2f57dc9825d7dce3e8dae2c4fb371915e189589bf355acd34f3f36941469c21ad99974eddc1ed22d7fe85bd99fff84c2700321825b4d54cbc57da7dbd3a5ffb
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -0,0 +1 @@
|
|||
358211cdc77501b1a001254d456886d788535930d0cdc730bb42ced23eaef7270d4c1da15a72c44dd91e7c82fed57b144ba427ff2b6f9b8a854870d678fbf51a5ec5f6d1bcfc36a4f2db501037caacd2276a7de086a86f9691eec6966bb0d04f85cbf3b0fb83475e2ee52adc5c2caab46e1a817a1e40ef7b67bb50819e9aff602dfcdecc86545addae01cd7f4103334ce29f417df00a408b1de9a0f87c3f106471a0c765133799dd86be49ac1bc145c367738384ffeb95583331e6e8891df33bd325aa23f36afd81feb5e3db780223c5bebd10fa36eeae18badd6301eb50cb07f4122496ee452ecb817183fdfe3ab4ff4cad08981dad320ff240d6f6e49466359ef39261c555b03e87d80b3832cf97fca1edb2c87f358b4a53ca9c2abb0d9fb0d7f5264b8c4b6e39cb301c8d9e8c2284b9358022b3508080c08597d46294297a0b722cf7ce05c163feb11a0971f524a9608dfc8759a0a5a1bfea439fafc838fd5943132c02fe98fa1a7085ced8b50e80ea41486459cd53e5ce42a3d2616f5371628d0d034d9e126bfec654e7bef78a3480698b82981e33a3108eebcf7ff33b92d8966fb640bae5002d98938b30361eb3f970e3b1780ab4d11a515943150833f0547f5905697756cbe1fc71c05ae8db880e27dce9afd70be0799fe86bea687bcece2f6e90fa9fb5c90c75c58a52430fe6a09129af71f45be974954547c15abde4bf607ef5d9ee623fa5ef5cc2a082b08358c5d590c21b9aa2161ff1a852153a55dd7f8756ba225419eb9da0b3275b8edafd404d37e7eb3094dd6d80e4583e61844d45dab0236e66a43cc3697d72d2854b11b85b0b5e77297a3d6b8221b569a3875e3fc40de245de5d99e1c8acc363e11b6e74944e58042d3bb69662193b37b5630e9ca7dae5fd812d6e1e1ea0078249f1c24422ed5a4ac285e5e2313bcde2f4324471140e3f01c7fbbb501850d0a27b13109c9e441fa3e4e62c7fd7d32a954df2d627ec96a8703ab49a2cdfa3278c8b81d505010f17a6b68309bca30d9199e27ce3eba84f9a67c5367adabf938a7bb1fb0b731e93adddb12c81c94e2b834f75b23954450e64100b379f543755cf9c634249f07c5c1287cfe991c22a66db577430baa2eed82a2541bebed5d5e9d8d9a32a5229e1f067b4822e4c002d7e2dabcf4ea61d4538dad459f924eacfef8f1ff2dd2fd7a38e95fcb01aff292f71454af38d7de8e19d388bda85e70ce51d994c1ca3b95611b9b391bcf18f74e816704bbb653c9181388c87604430859714e7f0058df37cfa24e2c1827677e0938af9c23a155b8f6b08884738ad95803cc0d29c8413e375749008bc6b65db8becd55b772cf39794d13684a2802a07620bbcd6ecdf602e2729d364fcb351b8167726748c3f76c374527d3a6a6904fdeb18fa0236dfee8e43997ac40e162732866b045ba35443d6778e0c0e65bb95a280989b3e8adf1bc909ea0eca0886e4274bfc3d07475048e6f10aa305ad093122a5dcaefde3e4d02c5b3e2a7f6a0be1dfaea66b8f1ee36910827078a150c8c1598aa54af0493776725a4aee541291671e537ea0982be8a61f933073748a6f98887c97d5994795a1b1e05e7ec5b98267590ec9cd458f515c8e1f4698cc0c40a264c682ae74b75b41afdef9c25849db3df4b9695ff06f7af17a2838b09597bfe28acaed443b54dd34acc9d0bcdae0c582894e3b38034f407e951b7e6afed2d9a06c8402d886e4e5fa8e5778094882fc119e2693c64855ddd8699b052c61a1fe16c0a69799b2f64b8ebb5530bd79b454f8dc466a0fd7561ef4ebce3094d32c3f5b074c698f8f7a0dcc1d3a1fcbbb57108c060fc8ef623c4f3bd49884d5793ada57c92fb5c6ffe4105ff113176711f8ae7fffe1387b0a9e78f338a92e3beca806ed978fd68f0de51f91e92e5de69fd99f70d8b06f3185fcc67cacdf96a4afb8fb8280223d93677553170c8aef23170ee663da7e9e8fe67998a656e99e18b258dd55dbb5d4d10724fe51a9a983866318ee3a36d7348ce1a7faf8f35499bb781c58875c8f2ce9dd764cba67fe757a873f625fc3e265b12e7753c35f7c507df0cc458c7ed51bb9d54314535ca57b55b5c8b2cdf465f7145e6d08d48950b29cb4faf166515b9e285779eb03bee1ee75bcc9cabc7e283c080c12835127f574987e9d860c818c5ddd4e032683c2d7948658fdb91f6c46cfdc977f2b2364a2542ca0a845571455dd64b4fff9283709ee968219e99151f614270270a4583834c029d85dce308a2bfa10ab5544a74ec5553c32712f9fbceab890094ecf833834c029d85dce308a2bfa10ab5544a75101ccf9715e524331ad3f4df401c6193c3cae667804e4c4eb4e793d276f3fa6bb976f70e1478db768802f1d9459b486b1dc15a77a7d09b6329548cd0f150180cb56dece31eac9f2eea0c0cd3d4b634e6f3d9a628718571a232e6186bdc9181dac710f8c6570befef677d6bf326f7ed1d6c21e491e15a63753d501c62fdf4d46555f90fc6bb442cdf8682961f3c17da992d16fb1acb15723059ac87f054e5f6aceb5315230f28fbffa2d24792dfadcd1110e8294bc61f0a526a058cf3c2a8c66aacd3cf4b51f5cd847fd5e1f65c40eb2be826d6431ef9931da3e9aa4f09c1a320f782d48eea75274b0e4ee4995e9d20489dd87d3c34192d5039f174f8d206bec968a151a8c96d7ff23667f3f6f9cbdccb7433038ff9a3c07266d6238974521a0d0e4b498e180ff126ce564a2dd60d15768c86c87a67ac8ab599fcff079c9862acd34c9a2d1836327a5400f30de40b77add582b7a23652739a7898a056c35eba463479cd1078dc07c3e2aa670d727757c102ab290d372bec01520cfe6d5125d5a595dff30fdd833a85def525170c9ab6e1000cf6172451c50ee871aa9da21ee94762667ffb14af384a675c9b0012290b4aa891f20c22a248a46245c4e6981b07541473c7c897d1a8cd668ad1391104e4a3443c3f46d257d882d9967691316d2285135b10d94db8a1f2fcc5319e050d4111202573809988887b46112c2370e8a6ff76972f8e36de07aa19203f8ed813f8ff86b8257b050c68e5ab2bb0680d50f98a2d16250368b9868e9741b654d3192a409d08b719b7d0b02d0d1ccb3f72fba01271888ef9d91a4ea140e1d2ab7b2c1f35e9bdb81e3ed00e051d7de07db4507e8982ccf1a52bb18be9cf7faf54199a138733f65a3cc2863d2284c60b315dc980943a1ee53a36e3b61f23a1c6618e325b91bbebe22e873f802520871ec7ad3f8696c89e20048e22e66701e36861b03ab2995a5bc5e54e3014f3cc2c5cc57e0b45e8886d3b16bd8a95b80c1d6abd41d2c666609457875bc8356d8ef40c91e66d8ea6ae7cc9214c6ff6f2b2fe0234bd36c834ed16b516ea6267399e9977b41ecf0df58e50a0cf0c18bdc6ab8d5f33baa79f33403b2b65633d5b2e778d9223b2116e3d93fa56b50b739f6c2c6d013fddf4d2915236b4abd5ca6deaeb0ae5ea17233bc
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -0,0 +1 @@
|
|||
858a029c9dc0acf038d4deab632b5fd5354a3119a8d3d6c22e0e34a9c611a80349f2a0ede136c8e502fbfba65eef66bda23a060b6254886705f3a50091145bf67042131a519012ee6dfeedf3e9d2e0c7c92d0a3cbbfbecb3598669738e0f6445d6b4a6d5555850689fbc94eaf18ef17adf1bebeb74ab64670d31c2c4ced69e77a1f6777f897cf22c2b0b92cf1872c3865b1a1919c74d8eebcbcef73fcd4666914f4cc763f892aceb9702cff3c5b80bd8fe9cbe8753f99e45c7e37093b793b9929a998c0e218f554f4a8c7ebf7579b08ec7d1b882ad50897d9078ffdf59321b697290844ebe59b84a05d4af22cd755572d7f2d87c60b3331e39317102807961fae4ce1c4e1115aa43f1c206224b1d5c1092c270fea56038e181e5df66d06a6568c732750d50616fe06bbcd8129d0e67d0977436497f97a371d3f726cd86e474ea070bfddbec4d874930810509160f50c4114659ffda8b38117af8ce49c6ff8d9c125b042952bda8be4e9d173b7aab24398aee7280f0532914cb4e61d9a790df29eb18b5b93ae79c20af0f1a36d7190e16bfe2493ade035d73d18232e83a645a3b3bac0d60223b0243cfcf185ee2859da1662ff24b1e15f4d2ee85696d21b2dfa3b3a9860508eda4860f736f5f735be42300dfea60d3fd43b089dc5526f0de29c846871c6e17dfe51b6ec1075712bb37cb8d7dbd6031a6a9388de4b98d5540340138f6b986f7ea1a9d16dd890084522042481d82577f7d4ee776a11547c73d9c8bfaf5a014015188b01a5fc4d56745a2c76b67dc254e91e820ec45b3c713efea85d46490625399a80e663e04bc6dfcc5a2ab78c3ed2facbe6c565e4fe6e78981cb20c83c21b50ec2cad8eea9568162e8adf37cfa24e2c1827677e0938af9c23a155143edad8ff021d68a8b3b100b3d2c0f6f47dc75aff189b33ab940bcbd3a1e097521ff03ad7ed61a61bbcd387424a1f5314b3b44be0307f35e29ebbd716c82b28c77f94a362a1b288e8d30cc237ae38fd809556d0d61fb0492b29d9fdf68dbae11184c95101c1354e6c47272e38fd30b1916a4ef9b2ac0187978675fdd12561f0154188fa7dd2f0ed010f41bfb57ac62ea785e09582448399ebe8feb6a3d216a0469a9983bad52fc964330c9da0c60046594025ae70fcb6527fd464b008389123d2e3c2b875c860a6f93ffb3d8fa9109084e24df6e9ce8227f9554921e313bc02daf7126beaf450b9f955d131a0acfb591411841421f704c8c8b38f4f686c056dc5f1adbe5267694c955320b4971d4119ac819229edd277f18ca8b300ff3bc566023fe7234234f1be883d64c1ef0c5852c0ba23d91a4d0932ac10ff20f5655f67ad8dd5d5dbe6811bda0f4e54668a74f11e09dc9c16ab86b50d16ed06af7ade3012930820c1b6f935b3fb11d3ceff2e7564ef017a975ac0f279b1e1d91327efa322038c4579bc1cb0d06a6110788d99eb7253db56c264cfd8e363fb85e5aa677871c524cdff9873ea02793f78f214614339309ef6af3c44505cd01d75a5c614bf0250d2c1df327618be081a1641bd533
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
title: "Callout example"
|
||||
slug: "callout-example"
|
||||
date: "1883-1-1"
|
||||
date: "1883-01-01"
|
||||
description: "This is an article with callout examples."
|
||||
author:
|
||||
- slug: andrew-pease
|
||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -0,0 +1 @@
|
|||
b811602cb36308a94550965e45dcaa8f2aa733c23e2fe479753e388db1882feafd3efba6fff1497934ce4f5bdcd508f9da891b22e1444773698ba08c1a0a7075f351e4d419e9845ae2de974de2063b3ca8cacec0b8310b92bd66fc339d3eaa2e2c4fda426ee25b50e8eae645cafd775bd8f6b6c367c28340463f0d159a024eeb7a616e1fb44af5c0cafb836d2e8ac26512ee67d746f950b170f6473d25df6767543c94d871b8b1a626cf50751b18168f77b82b9d6b3ca1cb416929ee5b4e453814c85dd8ee1cf71b3ed6ec11adb84e18d917e2f244122febae3ea88937dde94d85b6ff9bf87ef6390742bd6e34b4edc92539b88f435d41d2f3991e47dc667a318f83fe6017bf27524394efb36b697e63a3e2106df523f4e4abeb32affd56d83559b4ef8fc1fff0ae7241c7bea08701f3e7f65c4b3b42643b9d5904bc14cfc47caa40ec77c0b64e90fb95bc79271b361424dd89ee79e50f30e210a2d4e367280dfd1de7af2ee03bb3f393cead7ca59a80cfdac4fe6e4fcea3ea5b62fa869a06a9072db8211790aa45532e78e0ea66fee1cf54438101ce131f5c60b34e550103f979ec9dc945ba225addae34e2f659adaa2b6ff6b5bc62c09de92f5c5e39b90affa6d5c0bc68d9063ab316224cf0f2e3c61e56c70d0846268b7f84f1b2ee12d6347a1a668513dc5636d0a203e213414a49da483ccfb91c31e72da56d6c12f21654aa99e7178260a53d5326c274d5455a9a8ede419387e050f0c512e3f21c0d4740c65a555f6f64ff3b4a23edca3a7c4504056bcca0750b32a256a28c91a323ff1f672c331d377b5923afbb1eb198a24328e1d3a897ebb1ec25962ca3bde3ab73745879eaeb7856ce8f2e212404d2b68147afee2e6259b93c8f120960c6fca69431fd1d4c45f7265ee2647eadf9f70d6db85526e2f6c2cd4e55804cfdd61bcf98ff61e066bec2ae372ce2b322de584755f78e8fefa1618a285be2f928b602a8985065bede601846f61c67bd6ab71ac81d9f5333e496b2a6781209087946fb5389c3d598942e3e4914205c52d062a332a52c7ad1b26bb6431ec115e936b9daa7830111a1876aeab555b581c7511ffa397feb10dcc4289923a74f5f74efa07e886c09036daa3d37ab15c0bcf5128e04898f8ea88640b2aadd43591096163cd8f980f129ef765b100ae6d9b81a4d7f3e61b91def30da7a1bc51cf8194133215f1f41528d8e2df238219ffc66660519058bdc2464a660c1139945ceca8eb8aaa777ed3d505b9451778b601d02991e4ee4ff506f6d8fabb9573cfc1ed4bd964cffdf07d348b8b64fc4a06a34f0238285f65949ba3ee8fd60791fe4df2f97c8bb5b9d6455b594cb5044f7d95d81998dcb00969929b6c3f1c9c5e9d808b78f2cd93ed081e3d9d1d7470c05d92f445f39b0892175a678abfa3d39899e7ee7112940a77d28b7ef30da7a1bc51cf8194133215f1f41528d8e2df238219ffc66660519058bdc2464a660c1139945ceca8eb8aaa777ed3dbc05f5633b92b5b43ec6a641e6b61d17e5226cac72f3d7fcf7846b3a1152be879d69258d44fe8f38ab5a35af1c7c7ae0396e51670de40b38ef2b07b8253f469241ff58aa2cd9dd2a5814cccdb23a7bfdde34f1af21a7868a1e64b3c2da06d3796a423719dd73320453012bf11cd798148a210b91fd5053151e8f5897d10f844d6aeb2d84211db04ce711dbf6a5578689b48b8c3b8bea3c2283febd41fea89c68dc1f76a4bb771fcaca4e0a05a1ea1699a011a67551dfbf805ca48ee1009c3fc678f9d1433d025a4c48ac533301c7c7721dee28fb06aa8a00ed76ed2f9675fc92e695d4b3af40ec33989bbb0c91437cc1a0344bd8b62a125fa3d95f50387afeb7a21e277280acf0a8c97ed9c8e9bdc5c12e3c30b09d7933ddcef5ce1ce2faa523780f348a661372ff5fba6a898fe073ea506cbedb5b391413a72cf07e2649ccc20ab029a3a51364f56d64aa50e07460652a229c1bcf523477c93b1252313651b731908f89c7cc4964e6157786e33812b675855ed645eac685d204f56d639fa98e59de6820cfaa127187cc87a6349098fb24816d329af1ab8fabf312dc31c7174700367892e3da7795904dfb9100666b810149c756f8754d735fde7fb80a101c5ac4f77349e2def5b883a4dfb7c40b38e7a776adff5a6fa636b1dfcbdfd73c0dd1007e69f1d8e4dfd3100ec45196419af2cb64cfb18c80dc140ddc826af70900b4754f0d413d6e738743775be0b0b4ccd80286f3cc402b3071dfac6c5ac9a03fd33e8f23b4416657aabbce6b195883be44b5645fe1fb2887a25807f9743c806cae24015552e1794de7e25f7c8a655abbe7f005a3666e3b3554627b5e2513e703f556f81c84280593543c79fa889a01cda3badbfce7168a17b503a32cb2244af9470fa8d8bfdebafcb82e3732b362ed441928a122bad7b347395ebe59f8067b1c2740b26f58b13c2f4448a86a5ab9d69e8708939bc14a8d12fd1a3ee5aec6b96bef7d07112502e4b4d3ed4536303d9ebcfa083d18d9da6b794bc6b46ca10d9c50ec34ae21def4d995640f621586f22fc3847ed54f7df25cc0b0f13c7ef71a96bcded0c911d83ecb113a162c6d42dfb4ccc43dae790c8f272ef4da4b18b03a901e44d0478f405986aa7df56685e25c79afd590df142f1e6e23616724de7e778c712bb9c0f59e5da1ea01ac7b20065cc9f4f2f62f07a8fa71024f9a1695664004f03f87c318076c20a3994f84a957a7415baaba4d7bd9f62417293a768d43c92b2ef923757be40a2f95952443a1f44877dab45be5ceb6faf341ada61f0238025fc4d1e740dc8a230efb897f78fb44f2917880
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -0,0 +1 @@
|
|||
a7984d3c7e678f96b0761270e0392e63eb53206cf721e39d5c58537b52c89f93efba56fb08502cfca942d00352c3fd143ca3ea8ff9f67924becac878957d9d993a0bcc622e7ad0d645545d49f33f0dfe9e7dc02dbd99240e1eab7daad6ffae66b5293fcd51489c474b0c2958cf8b977c49c1a71c83a8b9f8afc1a42beccc1902211ff23ef054f61d6a73198a0729219dd8a85a0385b8d9ce8fe49cbf637d47ceb813e8b316ca39d12e7a3b99ad22ba2ed01fb409c3d9056cdeb1e808bb0f2f071d9da468b2bfb2550e5e5130777c661e38d71398bbb8957e54f2527ad39643cbd5259be4f701d023c5577ffe087eee5b3f3c0986112af3c412c5a47af41a639318ccb38d2e4b9fd55abc60ca68491495ea3f97bdc43a6cef2fc1f997178f89b5d03fa79ddee91973f28591b7d86a9aa1a7dea39ed5973827c9d7c75aa2f1ecbbeddfe3f35c0ea337c07d4a8c132b392f82a20225680dd91196d8cc699bd90980243693630302bb4ff7942cf57d495a3673b68d2ecd8602db28b290492169edb12950aff4e605c063f0d204e25f4085190b059b1a7efbd985af03e77824fb02ff7cb9a627689a8045413eeb8e457509c0890d5a76a5607c498913f32bc163b1e59ecc400f5f06a9f36f2c9ebc965aa7e1e24233323f2fb248c079104106647acdc8fbc8b31fe3e38ed290abaf2680e954bdedd7402276144fc32441a8e42cd36f06eacd613c6afb09b8be9f5130e8a35208d0e3d86cfaec99054fd29d524ff67a7101ec6fff26c5448d3e7b6cefd34bf0e0c062e7f025b6c8506dd993a5ab336a9c047ec137e3b77d903ffaa886698591a06c653d7ea3df5d4e1b91cd71f19f00dc55e2146116a5f4206ccfa4a2f39d7f83c2e3e75215f363efd8a3b7294ec88c437543b570d918aaa00c59e1addc00ce43bf6a6ba82a92a7bb6514f35013cee97b13fa40a9dd540c6291ef135ed5d3e1b6897132b2688e2a690b18c2d32b3af0d88ca138cb1ed85877da71a6bbe5a348564dde05a26724e8a1e2f10132df2b969a3fc01e825e602aba42f2bf2d858cf4f1cdef475ead44d48b3b8db2d98119651aa05c938c4dfd241656312befd01d35cd18208c0ae088f7b4cd017bb44a7e8686d5047e5932ff59ad3de2cf59de6a92737c665b5416d6f07514fe3cd1dc12ed30b0391b706873657dcc1118171ef0a7639410b20292ebbe6b08cb3d53c4a2d64a090281cd0d83b29ccada57e282a04e5235824c41aa61ccc674248e4c377cb21ce6be0f5e6090b97449968b4388414537553b2a4d30100df718a1ad3085d2f60d892a57998d63f682f59a5dd2832c2e4d13aa89ef9b0ef315cb308b32b82d095f45bbddd0afa06de916fa1a79a4be52059a3f67bd68343f5b6a37bfa9d37f60c30873455973b96890343a1be22531dc10942a38f9eb340711a453de5b07260633021cf51eec3c37c94330fbc529cc9af37fa3ff5407e88d6d5b9166742cc5c01c310aa26a24ac2e72a0e6204aa710f6dbefac752ebc7f61d022d17b43c2780b259aff535a0588fb5e9f4050a22698ab7d6e70223f4b38cc57d1b9fc979a6bab9efe763f523c4ec12cdb0971451461b5c418481443d3bcd72e4bb102129ffaa35d89a26e8418dd4ece03707a63bf48f873ac574825b76fa24eb76a99caa16be187abfd225f04c744e2ed641a255b0ac47ba796986d632416263b2bb54f45461b62a568c95f7927f4da498e7f0de32eab22a895f4bc9887b8d7ec02f0384236c69ed7a11af3c7923ff9df136cae8e7ea87d1d7fc93259d30a64a2516427ba8bc4c9ee6f511947a4e8f3cd36ef9d4b3ff2bb03748d0a7d6fa9df94001ec7e9909d00518dec42841257535055174fcbaa7354809ea30f0991a05d4afb4060feff4e43eeed7c09eb0e53fb01d5897b94b6eb90775b2f08143b8934cf53ada09b35fb26ff3bc2ac3e2ce724457221fbff122d9e6ac560f3852c49787c0381f862931b8513dbb09c833de448ac9812e7d535d6dba34f5668d3461bd0a2571c26a8ae3becc38e29f2465576b50f7a2512b24fd5
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -0,0 +1 @@
|
|||
592a3b67a8145248b50877c08173a6632b72a432548d24f72036b4a71753d2e6d430eaa09b937d8c38aff68068342260b87d5a94a4c1dbf582c2912437eb33040d7ddd230cfe257774b8fea330954ea22e0e39f9ff032087cd524bc7d2b2323f55abdc31c3c279860e695c494f96ca7a1c60c74592ffdd6858d9d579cba8513014c85dd8ee1cf71b3ed6ec11adb84e1880b3e86e3e10d5b63eb3c45640205b691914e70147073d6571968d5f2cde0bf9d313cfa33c69fced0436cf75f6daa8be7b518a2ac4fdacf4c09afc57d594a2c0c5c67237d35d9de2c8b699c8ff5723ef0a12ccf5cfa0588cb31c4b3e9e762e3eb2d5baea85b8c20d1f424a06b2c07045340454432ce4ead6b76f1013974d37e3eddfe3f35c0ea337c07d4a8c132b392f82a20225680dd91196d8cc699bd90980e8970d57ed1df37f9cf1c209ade1429207973bb98c9c5f401eafd3c5b21fc8faee0a1992e65fb793c17921de2f17bf3253d330d8d38bf2f2cb178c4ad67e8ab37964241c08fcd0519c067dd7839adcaa4468e76f60db15f40693a84e904692bc57b92db0772f5a25dfb76d7701cfead0d313cfa33c69fced0436cf75f6daa8beaa29cea90960153f89063363da07a8a056e093cb0427eda552c68e78a8b99ba28bd10305e5bafcd8d5105df45e53b7efd482c7727834adcab0c800f7cc79d08fc909ea0eca0886e4274bfc3d074750481809f442c7fc5f8d2e0fc08f2e5d1752dba1b6ce3093f7a089b2d74600e4241513c6076fe7a236348cd4a457fee947f8f517cd21e6f24ec6d502a60180a5b75bbb59842010adc91863bd8367d7cd6f556da215dd7d6f18219d85e8263e3803b9ee8dfc0984ab87f643980a5aae4020f02cb3ea2c836d93de0c583faaf02d6124b88783357fc01c9648f2b83afb161019b3c20f68d93811fb7549c838bde5fcb8e8e5b8b69b3f09ed172a591b00dee40f5b8f521f9dbd7e8c949dcc474844cbaf23f7df8339a0e51bdde8d26f97aa175fd6ff70d21e4cdf1d4b23be49f3388421e82c67110d73176dcf1fcb19f10ef72bc6363bd3e626eb36550a5d5928b58df777a39481fc2d211470dab6c57e92aa86f27d11ee5f45123cdae48c5d763d88c89d56599b7f6beef1d22abe53c7af1ed1c7bca8f59a830b3ad465aa82e6c42c2859ddbb7bd57ea94e7607a74273ea9ad21b2b804c476a8dc0c83310e59b86aff1b66db45ab93a2e53e5e08708c3c61cd3c8702537772e523d1f6dd13d9e6ce3dc38223240e54abdc0339a073c3e0f67a810dae6c521a5fea857d7adc011f3c3fd01612e1ab20079f85b80a370f29632ad82f3011dac796e6d8a7eec8946a3d475ae6f802964946ccf840b0b53ce7c7cb02a07831bdb4053675c342b6c4599e9d0c39a30580411166558a70ea2c852f0fb47e7b62df254de96fa3190cf1aade765242bbd7ea016102993fa30f39e1eff4e63bd9e4156ea84512145f90d233aa7fc3dd839d5f1b6af7072222dc1657eaa3ec54138c2054da9221b95b1904bcd4fe8bdcf538a56f410dbba287a2ed9a8aadf519d877181a61225fb8c404bfc59e7a2c808ee85f84e4ad2b10976ce1d36e1441b3b0e34076d31b253919c18b08826666f94a1b6f8c6e5b43b2dcf85d4df69ee9f4ca5ce43e32b74ee371f7d1d06b088beb8b7192972f49dd095519dd5cb83c541515f75ae116b88622beef02c30f1db6cf15c87e5062946c723dc7e23dad512b4954e6a8d2ada650c7fbfb18c86a20a4b5dea6a28af38d0d3dd1c79814114ea3fcc69c78eeecd3aa85c37e625ad33521f5a1588902808955af3dad111457173d834edf908d9f8976817e252549be8f4c3c92210cf14ac856ba4d63c08d22a11cad843bf135d4d5a642cff0f2152bbf6c36efe33e9b63572081523c66e52712b90ff31df0c92020897ac29cd2b262ffdf87d8c8a267801bbfc529a6e9673f42ff8ff819d855140c84833103b9daf7a3884a55a7afe08ea22d8d86bc71e67a1e8bb4797a6cefb520303801b6a7f59191da464f4b06f0e6af0196e8e75ebaa96d8841adb3f1096278966a7e880402445ae8fafb0dd6dc8df192cfbde1af6d941c814c491fe5483a3013c222195a8d92e8ead0e1d8f59eb6afd4ce9858f119e0dda4139e21a6606bd4488a2d80e0c94694211cf078e146b5c31206b29b337b0c18d6737e97e04ed670b63d930eb815c4d1676caae6d98f9b26d886e2fca456d1130c0046cdc7dd6d300b5f9c3d53df5fffac4604ed96d54a113efa34d1c11bea1e9437be5877261e3a6ee25de2b3495a6bbbefb3ec45159626146e92a42ee247c55a805ca1cb186fdaf55e22339a3cf165e9e23f4182df88dfd41dfe09e12f8806f440681247909e5906d30ad90a86e7cc7188827bc6925707bc990c73b648fec1e73988ff7aa8ce5f3e46990b5d547a4a91509254e5c874cf5574c115fb6b0068dc909ea0eca0886e4274bfc3d07475048ef857e1a67a4c9f53db169627b94e65c69f579ce8e2abe2e82839bd8e04926664ffde253236b80c43eb357017c39c7fa5dfe639faac5d194411c9a607fb6bc926570b432bce3da8822a9b64a7324dacee930578c28d856723ed9c2354e1e99a3
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue