master
sh 108 lines 4.61 KB
Raw
1 #!/usr/bin/env bash
2 # Scan durable SOW-related artifacts for sensitive values.
3 # Usage: .agents/sow/scan-sensitive.sh FILE...
4
5 set -uo pipefail
6
7 if [ "$#" -eq 0 ]; then
8 echo "usage: $0 FILE..." >&2
9 exit 2
10 fi
11
12 failures=0
13
14 if ! command -v perl >/dev/null 2>&1; then
15 echo "perl is required for sensitive-data scanning" >&2
16 exit 2
17 fi
18
19 scan_sensitive_file() {
20 local file="$1"
21 perl -ne '
22 chomp;
23 my $line = $_;
24 my @hits;
25 next if $ARGV =~ m{(^|/)\.agents/sow/scan-sensitive\.sh$}
26 && $line =~ /^\s*(?:push \@hits, "[^"]+"\s+(?:if|unless)\s+\$line =~|if \(\$line =~)/;
27
28 sub is_public_customer_ip {
29 my ($ip) = @_;
30 my @o = split(/\./, $ip);
31 return 0 unless @o == 4;
32 return 0 if grep { $_ !~ /^\d+$/ || $_ < 0 || $_ > 255 } @o;
33 return 0 if $o[0] == 10;
34 return 0 if $o[0] == 172 && $o[1] >= 16 && $o[1] <= 31;
35 return 0 if $o[0] == 192 && $o[1] == 168;
36 return 0 if $o[0] == 127;
37 return 0 if $o[0] == 169 && $o[1] == 254;
38 return 0 if $o[0] == 100 && $o[1] >= 64 && $o[1] <= 127;
39 return 0 if $o[0] == 0;
40 return 0 if $o[0] >= 224;
41 return 0 if $o[0] == 192 && $o[1] == 0 && $o[2] == 2;
42 return 0 if $o[0] == 198 && $o[1] == 51 && $o[2] == 100;
43 return 0 if $o[0] == 203 && $o[1] == 0 && $o[2] == 113;
44 return 1;
45 }
46
47 push @hits, "private-key-material" if $line =~ /-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----/;
48 push @hits, "aws-access-key" if $line =~ /\b(?:AKIA|ASIA)[0-9A-Z]{16}\b/;
49 push @hits, "github-token" if $line =~ /\b(?:github_pat_[A-Za-z0-9_]{20,}|gh[pousr]_[A-Za-z0-9_]{20,})\b/;
50 push @hits, "slack-token" if $line =~ /\bxox[baprs]-[A-Za-z0-9-]{20,}\b/;
51 push @hits, "openai-key" if $line =~ /\bsk-(?:proj-)?[A-Za-z0-9_-]{20,}\b/;
52 push @hits, "google-api-key" if $line =~ /\bAIza[0-9A-Za-z_-]{20,}\b/;
53 push @hits, "jwt" if $line =~ /\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b/;
54 push @hits, "credentialed-url" if $line =~ m{[a-z][a-z0-9+.-]*://[^/\s:@]+:[^/\s:@]+@}i && $line !~ m{x-access-token:(?:TOK|TOKEN|REDACTED|EXAMPLE|PLACEHOLDER|YOUR[_-]?TOKEN)\@github\.com}i;
55 push @hits, "bearer-token" if $line =~ /\bBearer\s+[A-Za-z0-9._~+\/=-]{16,}\b/i && $line !~ /\b(REDACTED|EXAMPLE|PLACEHOLDER|YOUR[_-]?(?:TOKEN|ACCESS[_-]?TOKEN|BEARER[_-]?TOKEN))\b/i;
56
57 if ($line =~ /\b(?:pass(?:word)?|passwd|pwd|api[_-]?key|secret|token|client[_-]?secret|private[_-]?key|access[_-]?key)\b\s*[:=]\s*["'\''`]?([^"'\''`\s<>{}\[\]&,]{8,})/i) {
58 my $value = lc $1;
59 push @hits, "credential-assignment" unless $value =~ /^(redacted|example|placeholder|changeme|change-me|xxx|xxxx|null|none|your[_-]?|dummy|sample|fake|test)/ || $value =~ /^\$/ || $value =~ /^(config|settings|options|opts|env|process\.env|os\.environ)\./ || $value =~ /^[a-z_][a-z0-9_.]*(token|secret|key|password)[a-z0-9_.]*$/ || $value =~ /^(tok|token)\@github\.com\b/;
60 }
61
62 if ($line =~ /\b(?:snmp[_-]?)?(?:community|community[_-]?string|rocommunity|rwcommunity)\b\s*[:=]\s*["'\''`]?([^"'\''`\s<>{}\[\]]{3,})/i) {
63 my $value = lc $1;
64 push @hits, "snmp-community" unless $value =~ /^(redacted|example|placeholder|changeme|change-me|xxx|xxxx|null|none)$/;
65 }
66
67 if ($line =~ /\b(?:customer|client|tenant|account|organization|org|community[ _-]?member)[ _-](?:name|id|identifier)\b\s*[:=]\s*["'\''`]?([^"'\''`<>\[\]{}][^"'\''`<>\[\]{}]{2,})/i) {
68 my $value = $1;
69 $value =~ s/^\s+|\s+$//g;
70 push @hits, "customer-or-private-identifier" unless $value =~ /^(redacted|example|placeholder|customer-|client-|tenant-|account-|org-|user|none|null)/i;
71 }
72
73 if ($line =~ /\b[A-Z0-9._%+\-]+@[A-Z0-9.\-]+\.[A-Z]{2,}\b/i) {
74 push @hits, "email-address" unless $line =~ /\b(example\.com|example\.org|example\.net|localhost)\b/i || $line =~ /\bgit\@github\.com[:\/]/i || $line =~ /x-access-token:(?:TOK|TOKEN|REDACTED|EXAMPLE|PLACEHOLDER|YOUR[_-]?TOKEN)\@github\.com/i;
75 }
76
77 if ($line =~ /\b(customer|client|tenant|account|community member|support|production|prod|log|trace|request|source ip|remote ip|x-forwarded-for|host ip)\b/i) {
78 while ($line =~ /\b((?:\d{1,3}\.){3}\d{1,3})\b/g) {
79 push @hits, "public-ip-address" if is_public_customer_ip($1);
80 }
81 }
82
83 for my $hit (@hits) {
84 print "$ARGV:$.:$hit\n";
85 }
86 ' "$file"
87 }
88
89 for file in "$@"; do
90 if [ ! -f "$file" ]; then
91 echo "missing file: $file" >&2
92 failures=$((failures + 1))
93 continue
94 fi
95
96 if ! hits=$(scan_sensitive_file "$file"); then
97 echo "failed to scan file: $file" >&2
98 failures=$((failures + 1))
99 continue
100 fi
101
102 if [ -n "$hits" ]; then
103 printf '%s\n' "$hits"
104 failures=$((failures + 1))
105 fi
106 done
107
108 exit "$failures"