0

Start blocking downloads when the file type is unsupported

Enterprise deep scanning for DLP violations has a specific list of
supported file types and the policy BlockUnsupportedFiletypes should
determine whether to blocked files not on that list. This CL introduces
a new download danger type for files blocked for that reason so that
we can, in future CLs, add UX specific to those files. It also generates
this danger type when appropriate, but uses the generic blocking UX.

Bug: 1060853
Change-Id: I6a670664d766bed860625cabcbd0d0e9a142e38e
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2101294
Commit-Queue: Daniel Rubery <drubery@chromium.org>
Reviewed-by: Dominique Fauteux-Chapleau <domfc@chromium.org>
Reviewed-by: Robert Kaplow <rkaplow@chromium.org>
Reviewed-by: Scott Violet <sky@chromium.org>
Reviewed-by: Marijn Kruisselbrink <mek@chromium.org>
Reviewed-by: Xing Liu <xingliu@chromium.org>
Cr-Commit-Position: refs/heads/master@{#750276}
This commit is contained in:
Daniel Rubery
2020-03-13 19:51:48 +00:00
committed by Commit Bot
parent 85cfe8262d
commit d6a3efe5ce
23 changed files with 154 additions and 20 deletions

@ -1182,6 +1182,10 @@ void ChromeDownloadManagerDelegate::CheckClientDownloadDone(
danger_type = download::DOWNLOAD_DANGER_TYPE_PROMPT_FOR_SCANNING;
is_pending_scanning = true;
break;
case safe_browsing::DownloadCheckResult::BLOCKED_UNSUPPORTED_FILE_TYPE:
danger_type =
download::DOWNLOAD_DANGER_TYPE_BLOCKED_UNSUPPORTED_FILETYPE;
break;
}
DCHECK_NE(danger_type,
download::DOWNLOAD_DANGER_TYPE_MAYBE_DANGEROUS_CONTENT);
@ -1312,6 +1316,12 @@ bool ChromeDownloadManagerDelegate::ShouldBlockFile(
if (IsDangerTypeBlocked(danger_type))
return true;
// TODO(crbug/1061111): Move this into IsDangerTypeBlocked once the UX is
// ready.
if (danger_type ==
download::DOWNLOAD_DANGER_TYPE_BLOCKED_UNSUPPORTED_FILETYPE)
return true;
switch (download_restriction) {
case (DownloadPrefs::DownloadRestriction::NONE):
return false;

@ -53,6 +53,8 @@ const char* GetDangerTypeString(
return "DeepScannedOpenedDangerous";
case download::DOWNLOAD_DANGER_TYPE_PROMPT_FOR_SCANNING:
return "PromptForScanning";
case download::DOWNLOAD_DANGER_TYPE_BLOCKED_UNSUPPORTED_FILETYPE:
return "BlockedUnsupportedFiletype";
case download::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS:
case download::DOWNLOAD_DANGER_TYPE_MAYBE_DANGEROUS_CONTENT:
case download::DOWNLOAD_DANGER_TYPE_USER_VALIDATED:

@ -235,6 +235,7 @@ bool DownloadItemModel::MightBeMalicious() const {
case download::DOWNLOAD_DANGER_TYPE_SENSITIVE_CONTENT_WARNING:
case download::DOWNLOAD_DANGER_TYPE_SENSITIVE_CONTENT_BLOCK:
case download::DOWNLOAD_DANGER_TYPE_PROMPT_FOR_SCANNING:
case download::DOWNLOAD_DANGER_TYPE_BLOCKED_UNSUPPORTED_FILETYPE:
return true;
case download::DOWNLOAD_DANGER_TYPE_DEEP_SCANNED_SAFE:
@ -283,6 +284,7 @@ bool DownloadItemModel::IsMalicious() const {
case download::DOWNLOAD_DANGER_TYPE_SENSITIVE_CONTENT_BLOCK:
case download::DOWNLOAD_DANGER_TYPE_DEEP_SCANNED_SAFE:
case download::DOWNLOAD_DANGER_TYPE_PROMPT_FOR_SCANNING:
case download::DOWNLOAD_DANGER_TYPE_BLOCKED_UNSUPPORTED_FILETYPE:
return false;
}
NOTREACHED();

@ -314,6 +314,7 @@ base::string16 DownloadUIModel::GetWarningText(const gfx::FontList& font_list,
return l10n_util::GetStringFUTF16(IDS_PROMPT_APP_DEEP_SCANNING,
elided_filename);
}
case download::DOWNLOAD_DANGER_TYPE_BLOCKED_UNSUPPORTED_FILETYPE:
case download::DOWNLOAD_DANGER_TYPE_DEEP_SCANNED_SAFE:
case download::DOWNLOAD_DANGER_TYPE_DEEP_SCANNED_OPENED_DANGEROUS:
case download::DOWNLOAD_DANGER_TYPE_ASYNC_SCANNING:

@ -747,6 +747,7 @@ base::string16 DownloadItemNotification::GetWarningStatusString() const {
return l10n_util::GetStringFUTF16(IDS_PROMPT_DOWNLOAD_CHANGES_SETTINGS,
elided_filename);
}
case download::DOWNLOAD_DANGER_TYPE_BLOCKED_UNSUPPORTED_FILETYPE:
case download::DOWNLOAD_DANGER_TYPE_PROMPT_FOR_SCANNING:
case download::DOWNLOAD_DANGER_TYPE_SENSITIVE_CONTENT_WARNING:
case download::DOWNLOAD_DANGER_TYPE_SENSITIVE_CONTENT_BLOCK:

@ -150,6 +150,7 @@ const char kDangerPasswordProtected[] = "passwordProtected";
const char kDangerTooLarge[] = "blockedTooLarge";
const char kDangerSensitiveContentWarning[] = "sensitiveContentWarning";
const char kDangerSensitiveContentBlock[] = "sensitiveContentBlock";
const char kDangerUnsupportedFileType[] = "unsupportedFileType";
const char kDangerDeepScannedSafe[] = "deepScannedSafe";
const char kDangerDeepScannedOpenedDangerous[] = "deepScannedOpenedDangerous";
const char kDangerPromptForScanning[] = "promptForScanning";
@ -203,7 +204,8 @@ const char* const kDangerStrings[] = {kDangerSafe,
kDangerSensitiveContentBlock,
kDangerDeepScannedSafe,
kDangerDeepScannedOpenedDangerous,
kDangerPromptForScanning};
kDangerPromptForScanning,
kDangerUnsupportedFileType};
static_assert(base::size(kDangerStrings) == download::DOWNLOAD_DANGER_TYPE_MAX,
"kDangerStrings should have DOWNLOAD_DANGER_TYPE_MAX elements");

@ -112,6 +112,7 @@ bool IsDownloadAllowedBySafeBrowsing(
case Result::SENSITIVE_CONTENT_WARNING:
case Result::DEEP_SCANNED_SAFE:
case Result::PROMPT_FOR_SCANNING:
case Result::BLOCKED_UNSUPPORTED_FILE_TYPE:
NOTREACHED();
return true;
}

@ -271,6 +271,7 @@ InterpretSafeBrowsingResult(safe_browsing::DownloadCheckResult result) {
case Result::POTENTIALLY_UNWANTED:
case Result::BLOCKED_PASSWORD_PROTECTED:
case Result::BLOCKED_TOO_LARGE:
case Result::BLOCKED_UNSUPPORTED_FILE_TYPE:
return ChromeNativeFileSystemPermissionContext::AfterWriteCheckResult::
kBlock;

@ -66,6 +66,7 @@ void MaybeOverrideDlpScanResult(DownloadCheckResultReason reason,
case DownloadCheckResult::BLOCKED_PASSWORD_PROTECTED:
case DownloadCheckResult::BLOCKED_TOO_LARGE:
case DownloadCheckResult::SENSITIVE_CONTENT_BLOCK:
case DownloadCheckResult::BLOCKED_UNSUPPORTED_FILE_TYPE:
callback.Run(deep_scan_result);
return;

@ -284,6 +284,16 @@ void DeepScanningRequest::OnScanComplete(BinaryUploadService::Result result,
AllowPasswordProtectedFilesValues::ALLOW_UPLOADS) {
download_result = DownloadCheckResult::BLOCKED_PASSWORD_PROTECTED;
}
} else if (result == BinaryUploadService::Result::UNSUPPORTED_FILE_TYPE) {
int block_unsupported_policy = g_browser_process->local_state()->GetInteger(
prefs::kBlockUnsupportedFiletypes);
if (block_unsupported_policy == BlockUnsupportedFiletypesValues::
BLOCK_UNSUPPORTED_FILETYPES_DOWNLOADS ||
block_unsupported_policy ==
BlockUnsupportedFiletypesValues::
BLOCK_UNSUPPORTED_FILETYPES_UPLOADS_AND_DOWNLOADS) {
download_result = DownloadCheckResult::BLOCKED_UNSUPPORTED_FILE_TYPE;
}
}
FinishRequest(download_result);

@ -48,11 +48,6 @@ std::string GetFileContentsBlocking(base::FilePath path) {
return contents;
}
int GetUnsupportedFiletypesPrefValue() {
return g_browser_process->local_state()->GetInteger(
prefs::kBlockUnsupportedFiletypes);
}
} // namespace
DownloadItemRequest::DownloadItemRequest(download::DownloadItem* item,
@ -89,22 +84,10 @@ void DownloadItemRequest::GetRequestData(DataCallback callback) {
bool dlp = deep_scanning_request().has_dlp_scan_request();
if (item_ && (malware || dlp) &&
!FileTypeSupported(malware, dlp, item_->GetTargetFilePath())) {
bool block_file = false;
switch (GetUnsupportedFiletypesPrefValue()) {
case BLOCK_UNSUPPORTED_FILETYPES_NONE:
case BLOCK_UNSUPPORTED_FILETYPES_UPLOADS:
block_file = false;
break;
case BLOCK_UNSUPPORTED_FILETYPES_DOWNLOADS:
case BLOCK_UNSUPPORTED_FILETYPES_UPLOADS_AND_DOWNLOADS:
block_file = true;
}
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(std::move(callback),
block_file
? BinaryUploadService::Result::UNSUPPORTED_FILE_TYPE
: BinaryUploadService::Result::SUCCESS,
BinaryUploadService::Result::UNSUPPORTED_FILE_TYPE,
Data()));
return;
}

@ -553,11 +553,29 @@ class DownloadProtectionServiceTest : public ChromeRenderViewHostTestHarness {
value);
}
void SetBlockUnsupportedFiletypePref(BlockUnsupportedFiletypesValues value) {
g_browser_process->local_state()->SetInteger(
prefs::kBlockUnsupportedFiletypes, value);
}
void SetSendFilesForMalwareCheckPref(SendFilesForMalwareCheckValues value) {
profile()->GetPrefs()->SetInteger(
prefs::kSafeBrowsingSendFilesForMalwareCheck, value);
}
void SetCheckContentCompliancePref(CheckContentComplianceValues value) {
g_browser_process->local_state()->SetInteger(prefs::kCheckContentCompliance,
value);
}
void SetUrlToCheckContentCompliance(const std::string& url_pattern) {
base::ListValue pattern_list;
pattern_list.Append(url_pattern);
g_browser_process->local_state()->Set(
prefs::kURLsToCheckComplianceOfDownloadedContent,
std::move(pattern_list));
}
// Helper function to simulate a user gesture, then a link click.
// The usual NavigateAndCommit is unsuitable because it creates
// browser-initiated navigations, causing us to drop the referrer.
@ -3050,6 +3068,78 @@ TEST_P(DeepScanningDownloadTest, LargeFileBlockedByPreference) {
}
}
TEST_P(DeepScanningDownloadTest, UnsupportedFiletypeBlockedByPreference) {
if (!base::FeatureList::IsEnabled(kMalwareScanEnabled) &&
!base::FeatureList::IsEnabled(kContentComplianceEnabled))
return;
base::FilePath test_file;
ASSERT_TRUE(base::PathService::Get(chrome::DIR_TEST_DATA, &test_file));
test_file = test_file.AppendASCII("safe_browsing")
.AppendASCII("download_protection")
.AppendASCII("signed.exe");
NiceMockDownloadItem item;
PrepareBasicDownloadItemWithFullPaths(
&item, {"http://www.evil.com/signed.exe"}, // url_chain
"http://www.google.com/", // referrer
test_file, // tmp_path
temp_dir_.GetPath().Append(
FILE_PATH_LITERAL("signed.exe"))); // final_path
content::DownloadItemUtils::AttachInfo(&item, profile(), nullptr);
TestBinaryUploadService* test_upload_service =
static_cast<TestBinaryUploadService*>(
sb_service_->GetBinaryUploadService(profile()));
test_upload_service->SetResponse(
BinaryUploadService::Result::UNSUPPORTED_FILE_TYPE,
DeepScanningClientResponse());
EXPECT_CALL(*sb_service_->mock_database_manager(),
MatchDownloadWhitelistUrl(_))
.WillRepeatedly(Return(false));
EXPECT_CALL(*binary_feature_extractor_.get(), CheckSignature(tmp_path_, _))
.Times(2);
EXPECT_CALL(*binary_feature_extractor_.get(),
ExtractImageFeatures(
tmp_path_, BinaryFeatureExtractor::kDefaultOptions, _, _))
.Times(2);
SetCheckContentCompliancePref(CheckContentComplianceValues::CHECK_DOWNLOADS);
SetUrlToCheckContentCompliance("www.evil.com");
{
SetBlockUnsupportedFiletypePref(
BlockUnsupportedFiletypesValues::BLOCK_UNSUPPORTED_FILETYPES_DOWNLOADS);
PrepareResponse(ClientDownloadResponse::SAFE, net::HTTP_OK, net::OK);
RunLoop run_loop;
download_service_->CheckClientDownload(
&item,
base::BindRepeating(&DownloadProtectionServiceTest::CheckDoneCallback,
base::Unretained(this), run_loop.QuitClosure()));
run_loop.Run();
EXPECT_TRUE(IsResult(DownloadCheckResult::BLOCKED_UNSUPPORTED_FILE_TYPE));
EXPECT_TRUE(HasClientDownloadRequest());
}
{
SetBlockUnsupportedFiletypePref(
BlockUnsupportedFiletypesValues::BLOCK_UNSUPPORTED_FILETYPES_NONE);
PrepareResponse(ClientDownloadResponse::SAFE, net::HTTP_OK, net::OK);
RunLoop run_loop;
download_service_->CheckClientDownload(
&item,
base::BindRepeating(&DownloadProtectionServiceTest::CheckDoneCallback,
base::Unretained(this), run_loop.QuitClosure()));
run_loop.Run();
EXPECT_TRUE(IsResult(DownloadCheckResult::UNKNOWN));
EXPECT_TRUE(HasClientDownloadRequest());
ClearClientDownloadRequest();
}
}
TEST_F(DownloadProtectionServiceTest, NativeFileSystemWriteRequest_NotABinary) {
auto item = PrepareBasicNativeFileSystemWriteItem(
/*tmp_path=*/FILE_PATH_LITERAL("a.txt.crswap"),

@ -29,6 +29,7 @@ enum class DownloadCheckResult {
SENSITIVE_CONTENT_BLOCK,
DEEP_SCANNED_SAFE,
PROMPT_FOR_SCANNING,
BLOCKED_UNSUPPORTED_FILE_TYPE,
};
// Enum to keep track why a particular download verdict was chosen.
@ -69,6 +70,7 @@ enum DownloadCheckResultReason {
REASON_SENSITIVE_CONTENT_BLOCK = 32,
REASON_DEEP_SCANNED_SAFE = 33,
REASON_ADVANCED_PROTECTION_PROMPT = 34,
REASON_BLOCKED_UNSUPPORTED_FILE_TYPE = 35,
REASON_MAX // Always add new values before this one.
};

@ -223,6 +223,7 @@ base::string16 DownloadDangerPromptViews::GetMessageBody() const {
IDS_PROMPT_DOWNLOAD_CHANGES_SETTINGS,
download_->GetFileNameToReportUser().LossyDisplayName());
}
case download::DOWNLOAD_DANGER_TYPE_BLOCKED_UNSUPPORTED_FILETYPE:
case download::DOWNLOAD_DANGER_TYPE_PROMPT_FOR_SCANNING:
case download::DOWNLOAD_DANGER_TYPE_SENSITIVE_CONTENT_WARNING:
case download::DOWNLOAD_DANGER_TYPE_SENSITIVE_CONTENT_BLOCK:

@ -1248,6 +1248,7 @@ gfx::ImageSkia DownloadItemView::GetWarningIcon() {
GetNativeTheme()->GetSystemColor(
ui::NativeTheme::kColorId_DefaultIconColor));
case download::DOWNLOAD_DANGER_TYPE_BLOCKED_UNSUPPORTED_FILETYPE:
case download::DOWNLOAD_DANGER_TYPE_DEEP_SCANNED_SAFE:
case download::DOWNLOAD_DANGER_TYPE_DEEP_SCANNED_OPENED_DANGEROUS:
case download::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS:

@ -75,6 +75,8 @@ const char* GetDangerTypeString(download::DownloadDangerType danger_type) {
return "DEEP_SCANNED_SAFE";
case download::DOWNLOAD_DANGER_TYPE_DEEP_SCANNED_OPENED_DANGEROUS:
return "DEEP_SCANNED_OPENED_DANGEROUS";
case download::DOWNLOAD_DANGER_TYPE_BLOCKED_UNSUPPORTED_FILETYPE:
return "BLOCKED_UNSUPPORTED_FILE_TYPE";
case download::DOWNLOAD_DANGER_TYPE_PROMPT_FOR_SCANNING:
case download::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS:
case download::DOWNLOAD_DANGER_TYPE_MAYBE_DANGEROUS_CONTENT:

@ -76,6 +76,11 @@ enum DownloadDangerType {
// recommended this file be deep scanned.
DOWNLOAD_DANGER_TYPE_PROMPT_FOR_SCANNING = 17,
// The download has a file type that is unsupported for deep scanning, and
// should be blocked according to policy. See the BlockUnsupportedFiletypes
// policy for details.
DOWNLOAD_DANGER_TYPE_BLOCKED_UNSUPPORTED_FILETYPE = 18,
// Memory space for histograms is determined by the max.
// ALWAYS ADD NEW VALUES BEFORE THIS ONE.
DOWNLOAD_DANGER_TYPE_MAX

@ -88,6 +88,8 @@ download::DownloadDangerType ToContentDownloadDangerType(
return download::DOWNLOAD_DANGER_TYPE_DEEP_SCANNED_OPENED_DANGEROUS;
case DownloadDangerType::PROMPT_FOR_SCANNING:
return download::DOWNLOAD_DANGER_TYPE_PROMPT_FOR_SCANNING;
case DownloadDangerType::BLOCKED_UNSUPPORTED_FILETYPE:
return download::DOWNLOAD_DANGER_TYPE_BLOCKED_UNSUPPORTED_FILETYPE;
case DownloadDangerType::INVALID:
NOTREACHED();
return download::DOWNLOAD_DANGER_TYPE_MAX;
@ -135,6 +137,8 @@ DownloadDangerType ToHistoryDownloadDangerType(
return DownloadDangerType::DEEP_SCANNED_OPENED_DANGEROUS;
case download::DOWNLOAD_DANGER_TYPE_PROMPT_FOR_SCANNING:
return DownloadDangerType::PROMPT_FOR_SCANNING;
case download::DOWNLOAD_DANGER_TYPE_BLOCKED_UNSUPPORTED_FILETYPE:
return DownloadDangerType::BLOCKED_UNSUPPORTED_FILETYPE;
default:
NOTREACHED();

@ -44,6 +44,7 @@ enum class DownloadDangerType {
DEEP_SCANNED_SAFE = 15,
DEEP_SCANNED_OPENED_DANGEROUS = 16,
PROMPT_FOR_SCANNING = 17,
BLOCKED_UNSUPPORTED_FILETYPE = 18,
};
// DownloadId represents the id of a DownloadRow into the DownloadDatabase.

@ -72,6 +72,7 @@ DownloadDangerType IntToDownloadDangerType(int danger_type) {
case DownloadDangerType::DEEP_SCANNED_SAFE:
case DownloadDangerType::DEEP_SCANNED_OPENED_DANGEROUS:
case DownloadDangerType::PROMPT_FOR_SCANNING:
case DownloadDangerType::BLOCKED_UNSUPPORTED_FILETYPE:
return static_cast<DownloadDangerType>(danger_type);
case DownloadDangerType::INVALID:
@ -129,6 +130,9 @@ std::ostream& operator<<(std::ostream& stream, DownloadDangerType danger_type) {
<< "history::DownloadDangerType::DEEP_SCANNED_OPENED_DANGEROUS";
case DownloadDangerType::PROMPT_FOR_SCANNING:
return stream << "history::DownloadDangerType::PROMPT_FOR_SCANNING";
case DownloadDangerType::BLOCKED_UNSUPPORTED_FILETYPE:
return stream
<< "history::DownloadDangerType::BLOCKED_UNSUPPORTED_FILETYPE";
}
NOTREACHED();
return stream;

@ -27,6 +27,7 @@
#include "build/build_config.h"
#include "components/download/database/in_progress/download_entry.h"
#include "components/download/public/common/download_create_info.h"
#include "components/download/public/common/download_danger_type.h"
#include "components/download/public/common/download_features.h"
#include "components/download/public/common/download_file.h"
#include "components/download/public/common/download_interrupt_reasons.h"
@ -1135,7 +1136,9 @@ int DownloadManagerImpl::NonMaliciousInProgressCount() {
it.second->GetDangerType() !=
download::DOWNLOAD_DANGER_TYPE_DANGEROUS_HOST &&
it.second->GetDangerType() !=
download::DOWNLOAD_DANGER_TYPE_POTENTIALLY_UNWANTED) {
download::DOWNLOAD_DANGER_TYPE_POTENTIALLY_UNWANTED &&
it.second->GetDangerType() !=
download::DOWNLOAD_DANGER_TYPE_DEEP_SCANNED_OPENED_DANGEROUS) {
++count;
}
}

@ -16992,6 +16992,7 @@ to ensure that the crash string is shown properly on the user-facing crash UI.
<int value="15" label="DEEP_SCANNED_SAFE"/>
<int value="16" label="DEEP_SCANNED_OPENED_DANGEROUS"/>
<int value="17" label="PROMPT_FOR_SCANNING"/>
<int value="18" label="BLOCKED_UNSUPPORTED_FILETYPE"/>
</enum>
<enum name="DownloadLocationDialogResult">
@ -57584,6 +57585,7 @@ https://www.dmtf.org/sites/default/files/standards/documents/DSP0134_2.7.1.pdf
<int value="32" label="SENSITIVE_CONTENT_BLOCK"/>
<int value="33" label="DEEP_SCANNED_SAFE"/>
<int value="34" label="ADVANCED_PROTECTION_PROMPT"/>
<int value="35" label="BLOCKED_UNSUPPOTED_FILETYPE"/>
</enum>
<enum name="SBClientDownloadCheckResult">
@ -57601,6 +57603,7 @@ https://www.dmtf.org/sites/default/files/standards/documents/DSP0134_2.7.1.pdf
<int value="11" label="SENSITIVE_CONTENT_BLOCK"/>
<int value="12" label="DEEP_SCANNED_SAFE"/>
<int value="13" label="PROMPT_FOR_SCANNING"/>
<int value="14" label="BLOCKED_UNSUPPORTED_FILETYPE"/>
</enum>
<enum name="SBClientDownloadExtensions">

@ -186454,6 +186454,8 @@ regressions. -->
<affected-histogram
name="Download.DownloadDangerPrompt.BlockedPasswordProtected"/>
<affected-histogram name="Download.DownloadDangerPrompt.BlockedTooLarge"/>
<affected-histogram
name="Download.DownloadDangerPrompt.BlockedUnsupportedFiletype"/>
<affected-histogram name="Download.DownloadDangerPrompt.DangerousContent"/>
<affected-histogram name="Download.DownloadDangerPrompt.DangerousFile"/>
<affected-histogram name="Download.DownloadDangerPrompt.DangerousHost"/>
@ -186475,6 +186477,8 @@ regressions. -->
<suffix name="BlockedPasswordProtected"
label="File marked BLOCKED_PASSWORD_PROTECTED"/>
<suffix name="BlockedTooLarge" label="File marked BLOCKED_TOO_LARGE"/>
<suffix name="BlockedUnsupportedFiletype"
label="File marked BLOCKED_UNSUPPORTED_FILETYPE"/>
<suffix name="DangerousContent" label="File marked DANGEROUS_CONTENT"/>
<suffix name="DangerousFile" label="File marked DANGEROUS_FILE"/>
<suffix name="DangerousHost" label="File marked DANGEROUS_HOST"/>