You could write a wrapper function in Bash and name it scp that uses getopts to process the options into a new array leaving the source and destination in the positional parameters, then check those for the presence of "@" and ":" and, if absent, issue an error message. If they're present, then call the real scp using something like command scp "${args[@]}" "$source" "$dest". The command command causes Bash to call the program that's found in the PATH instead of the function by the same name.
Since, at least on my system, scp only supports the short option type, getopts (which also only supports short options) will work fine for this.
The function would look something like this (untested):
scp () {
# wrapper function to prevent scp of local-only files
options=":346BCpqrTvc:F:i:J:l:o:P:S:"
while getopts $options option
do
case $option in
3 | 4 | 6 | B | C | p | q | r | T | v)
args+=($option)
;;
c | F | i | J | l | o | P | S)
args+=($option "$OPTARG")
;;
\? ) echo "Unknown option: -$OPTARG" >&2; return 1;;
: ) echo "Missing option argument for -$OPTARG" >&2; return 1;;
* ) echo "Unimplemented option: -$option" >&2; return 1;;
esac
done
shift $((OPTIND - 1))
if [[ $1 != *@*:* && $2 != *@*:* ]]
then
echo "Local-only copy not permitted"
echo "to override, use 'command scp ARGS'"
return 1
fi
command scp "${args[@]}" "$1" "$2"
}
The options in this script reflect those accepted by scp at the time of this writing, but they may change over time. That is one of the risks presented by wrapper scripts such as this.